Wie in jeder anderen Programmiersprache auch, werden die Datentypen in C in verschiedene Kategorien unterteilt. Je nach Verwendungsart, Größe und Funktion. Grob gesehen gibt es nur drei große Klassen an Datentypen, die sich wie folgt einteilen lassen:
3.1. Datentypen für ganze Zahlen (Festkomma)
Dieser Gruppe gehören vier Datentypen an, die sich ausschließlich durch ihre Größe unterscheiden. Beginnend mit dem kleinsten möchte ich nun die Fähigkeiten und die Einsatzgebiete dieser Typen erklären.
3.1.1. char
Eine acht Bit lange Ganzzahl, deren Abkürzung für \"Charakter \" steht. Eine Variable, die vom Typ char ist, kann Zahlen im Bereich von 0 bis 255 oder von -128 bis +127 annehmen. Durch die bereits erwähnten Schlüsselwörter signed bzw. unsigned kann zwischen den beiden Wertebereichen gewechselt werden. Wird eine Variable nur mit char deklariert, so ist sie standardmäßig signed.
Bsp.:
char cChar = 100; //enthält 100
char cChar = -10; //enthält -10
signed char cChar = -10; //wie Vorzeile
char cChar = 200; //Würde zwar keinen Fehler während des Kompilieren //erzeugen, ist jedoch ein Fehlergrund, da cChar in //diesem Fall signed ist und deshalb nur bis +127 //reicht.
3.1.2. int
Je nach Betriebssystem ist dieser Datentyp 16 oder 32 Bit lang. Die Abkürzung int steht für \"Integer\" (integere - lat.: ganz). Ausgehend vom 16 Bit Typ reicht der Wertebereich dieses Typs von 0 bis 65535 oder von - 32768 bis +32767. Auch hier kann man die Bereiche mit signed bzw. unsigned ändern.
Bsp.:
int iValue = 100; //enthält 100
int iValue = -10; //enthält -10
signed int iValue = -10; //wie Vorzeile
3.1.3. short
Eigentlich nur eine Abart von int, ist short immer 16 Bit lang und sollte deshalb anstelle von int verwendet werden. Wertebereich und Beispiel siehe 3.1.2.
3.1.4. long
Long ist int mit 32 Bit. Long stellt in C den größten Datentyp für Festkommazahlen dar.
3.2. Datentypen für Fließkommazahlen
Die Verwendung von Fließkommazahlen ist in den meisten Programmen erforderlich, da es nur selten Berechnungen gibt, die ohne Komma auskommen. Die Darstellung solcher Zahlen, welche nach IEEE 754 floating-point standard genormt ist, ist wesentlich komplizierter als bei Festkommazahlen. Der Wert einer Fließkommazahl entspricht nämlich nicht direkt ihrem binären Wert, sondern besteht aus drei Teilen. Eine Fließkommazahl sieht deshalb folgend aus:
Die obige Darstellung entspricht einem sogenannten short real Datentyp, der in C als float bekannt ist. Insgesamt gibt es nur zwei verschiedene Grundtypen von Fließkommazahlen, die ähnlich wie die Festkommazahlen, in C durch voranstellen von Schlüsselwörter verändert werden können (signed und unsigend haben hier jedoch keine Bedeutung!).
Die Darstellung von speziellen Zahlen ist in der Fließkommarechnung wie folgt gelöst:
Nulldarstellung (zero): Exponent ist 0, Mantisse ist 0
Unendlich (infinity): Exponent alles 1, Mantisse ist 0
Keine Zahl (not a number): Exponent alles 1, Mantisse alles 0
3.2.1. float
Ein 32 Bit Datentyp, dessen Abkürzung für \"floating\" (engl. fließend) steht. Eine Variable, die als float deklariert wurde, besitzt ein Vorzeichenbit, acht Exponentenbits und 23 Bits für die Mantisse. Der Wertebereich liegt zwischen 3.4*10-38 und 3.4*1038. Eine float - Zahl ist eine Fließkommazahl einfacher Genauigkeit. Einfache Genauigkeit, da bei jeder Fließkommazahl der Wert nur angenähert werden kann. Diese Annäherung ist besser, je länger die Mantisse ist. Dies ist vor allem bei Anwendungen, die mit Geldbeträgen rechnen wichtig, da hier Rundungsfehler auftreten.
Bsp.:
float fValue = 1.5; //Zuweisung von 1,5
float fValue = -10.3; //Zuweisung von -10,3
3.2.2. double
Der Datentyp double ist 64 Bit lang und ist vom Prinzip her float mit doppelter Genauigkeit. Dieser Typ besteht aus einem Vorzeichenbit, elf Exponentenbits und 52 (doppelte Anzahl) Bits für die Mantisse. Der Wertebereich liegt zwischen 1.7 * 10-308 bis 1.7 * 10308. Double bietet nun eine doppelt so hohe Genauigkeit wie float, ist aber unter Umständen immer noch nicht für den Umgang mit Geld geeignet. Der letzte und höchste Typ an Zahl, den C zur Verfügung stellt, ist long double. Mit 80 Bit Länge und dem damit größten Wertebereich in C von 3.4 * 10-4932 bis 1.1 * 104932 die größte darstellbare Zahl.
Bsp.:
double dValue = 1.5; //Zuweisung von 1,5
long double dValue = -10.3; //Zuweisung von -10,3
Bevor die nächste Gruppe von Datentypen vorgestellt werden soll, noch eine abschließende Übersicht:
Typ Länge Wertebereich
unsigned char 8 Bits 0 bis 255
char 8 Bits -128 bis 127
enum 16 Bits -32768 bis 32767
unsigned int 16 Bits 0 bis 65535
short int 16 Bits -32768 bis 32767
int* 16 Bits -32768 bis 32767
unsigned long 32 Bits 0 bis 4294967295
long 32 Bits -2147483648 bis 2147483647
float 32 Bits 3,4 * 10-38 bis 3,4 * 1038
double 64 Bits 1.7 * 10-308 bis 1.7 * 10308
long double 80 Bits 3.4 * 10-4932 bis 1.1 * 104932
3.3. Benutzerdefinierte Datentypen
Das Faktum, das C so hoch angerechnet wird, ist die Flexibilität in der Gestaltung eigener Datentypen. Es gibt hier eine Unzahl an Möglichkeiten und Variationen, die an das jeweilige Anwendungsgebiet angepasst werden können.
3.3.1. Strings
Ein String (engl.:Schnur) ist eine aufeinanderfolgende Anordnung von ASCII - Zeichen, die in einem char- Feld stehen. Eine Zeichenkette sieht in C folgendermaßen aus:
Jedes Kästchen repräsentiert eine Variable vom Typ char, die mit einer Zahl aus der ASCII Tabelle gefüllt ist. Um das Ende einer solchen Kette zu markieren, wird in das letzte Feld eine binäre Null geschrieben. Dieses Zeichen, darf also nicht mitten im String auftauchen, da es ihn sonst bereits in der Mitte abschließt. Bei der Übergabe an Unterprogramme ist gleich zu verfahren, wie bei jedem anderen Feld. Auch die Rückgabe muss gleich erfolgen.
Bsp.:
char *String = \"Text\"; //Zulässig, da der Compiler selbständig eine Null anhängt
char *String;
String = \"Text\"; //Nicht zulässig
Die obigen Beispiele zeigen Zeichenketten ohne festgelegtes Ende.
char String[80]; //Eine Zeichenkette, die auf 79 Zeichen beschränkt ist
Um Zeichenketten zu verbinden, ihre Länge zu ermitteln, sie abzuschneiden oder sonstiges mit ihnen zu machen, bietet C eine Bibliothek, in der solche Funktionen vorhanden ist: string.h.
Folgende Funktionen zur Stringbehandlung sind dort verfügbar:
Name Bezeichnung
size_t strlen(const char *s) Ermittelt die Länge eines Strings (ohne 0)
char* strcpy(char *dest, const char *source) Kopiert den String source in den String dest, wobei dest zurückgegeben wird
char* strcat(char *dest, const char *source) Fügt den String source an den String dest an und gibt das Ergebnis zurück
int strcmp(const char *s1, const char *s2) Vergleicht String s1 mit s2 und liefert 0 zurück, wenn sie identisch sind
Es gibt hier natürlich noch andere Funktionen. Die oben genannten sind allerdings die meist gebrauchten. Um zu Veranschaulichen, wie eine solche Funktion aussieht, soll an dieser Stelle ein möglicher Quellcode der strlen Funktion beschrieben werden.
int strlen(const char *s)
{
for(int nLen = 0; s[nLen]; nLen++);
return nLen;
}
3.3.2. enum
Enum ist ein Beispiel für die Möglichkeit, gut lesbaren Code zu schreiben. Dieser Typ ist an sich int, jedoch können mit ihm Folgen von Konstanten definiert werden.
Bsp.:
enum modes = {LASTMODE=-1, BW=0, C40, BW80};
enum bool = {true=1, false=0};
Mit obigem Quellcode haben wir zwei \"neue\" Datentypen erschaffen, die, verwendet im Code, die Lesbarkeit stark erhöhen. Eingesetzt können sie auf verschiedene Arten werden. Zum Einen ist es möglich, eine Variable vom Typ modes zu generieren und diese auf die Elemente davon abzufragen.
Bsp.:
modes View;
View = LASTMODE;
If(View == LASTMODE)
...
Dies kann bei allen enum Typen angewandt werden. Jene Elemente, denen eine Zahl zugewiesen wurde, können auch über diese abgefragt werden.
Bsp.:
bool bValue;
...
if(bValue == true) oder if(bValue)
Beide Möglichkeiten sind hier erlaubt.
3.3.3. typedef
Mit typedef können \"neue\" Datentypen erzeugt werden, die allerdings auf bereits bestehenden aufbauen.
Bsp.:
typedef unsigned long DWORD;
typedef char str40[41];
Im ersten Beispiel ersetzen wir die lange und umständliche Schreibweise unsigned long durch die kürzere DWORD. Nun kann bei jeder Variable, die vom Typ unsigned long sein soll, ganz einfach nur DWORD geschrieben werden.
3.3.4. struct
Eine Struktur ist eine Zusammenfassung von verschiedenen Datentypen zu einem Record. Durch die Verwendung einer Struktur ist es möglich, komplexere Objekte auf einfachem Weg zu generieren. Eine Struktur wird folgend definiert:
struct [Name]{
[Datentyp] Variablenname;
[Datentyp] Varianlenname;
...
} [instanzierte Variablen];
Was möglicherweise verwirrend ist, ist die Tatsache, dass Strukturen nicht zwangsweise einen Namen haben müssen. Wenn Sie jedoch keinen Namen besitzen, muss zumindest eine Variable instanziert werden, die diese Struktur repräsentiert. Eine Struktur ohne Namen, kann im weiteren Programmverlauf jedoch nicht mehr zur Deklaration von Variablen verwendet werden.
Grundsätzlich sieht eine Strukturdefinition jedoch so aus:
struct Person{
char szName[81];
char szVName[81];
int nAlter;
};
Eine namenlose Struktur würde demnach folgend aussehen:
struct{
char szName[81];
char szVName[81];
int nAlter;
} MeinFreund;
Diese Struktur heißt nicht etwa \"MeinFreund\", sondern eine Instanz dieser Struktur heißt \"MeinFreund\". Die Struktur kann aufgrund dieser Deklaration nicht weiter verwendet werden.
3.3.5. union
Eine union ähnelt vom Aufbau einer Struktur. Sie erlaubt es, mehrere Variablen verschiedenen Typs zu deklarieren, die sich den selben Speicherplatz teilen. Aussehen:
union [Name]{
{Datentyp} Name;
{Datentyp} Name;
...
} [instanzierte Variablen];
Es gelten bei der union die gleichen Regelungen, wie bei einer Struktur, was Namensgebung und Instanzierung der Variablen angeht. Der Unterschied liegt nun darin, dass immer nur eine der Variablen der union aktiv ist, das heißt den Speicherplatz belegt. Beispiel:
union int_long{
int i;
long j;
} test;
test.i = 10; //Zu diesem Zeitpunkt ist die int Variable aktiv, das heißt, dass der //Speicherplatz der union i zugewiesen wird
test.j = 100; //Sobald auf das andere Objekt zugegriffen wird, übernimmt diesen den //Speicherplatz, i ist ab diesem Zeitpunkt \"verstorben\"
Eine union beansprucht soviel Speicherplatz, wie ihr größtes Objekt.
3.4. Dynamische Speicheranforderung
Bei bisherigen Verfahren der Speicheranforderung, muss bereits zur Übersetzungszeit feststehen, wie groß z.B. ein Feld ist. Diese Variable wird dann im Stack abgelegt und bleibt während eines ganzen Blockes aktiv. Wird der Block verlassen, wird auch der Speicher, den die Variable benützt wieder freigegeben. Um allerdings eine größtmögliche Flexibilität während der Programmierung zu gewährleisten, gibt es auch die Möglichkeit, Speicher dynamisch, das heißt während der Laufzeit, anzufordern. Hierzu gibt es in C die beiden Funktionen malloc und free. Die Funktion malloc reserviert einen Bereich im Heap und free gibt ihn wieder frei. In Turbo C sind je nach Speichermodell unterschiedliche Größen vom Heap erhältlich. Im Modell large steht der gesamte RAM zur Verfügung.
Bsp.:
char *str;
str = (char *)malloc(15)
strcpy(str, \"Hello World\");
free(str);
Diese Art der dynamischen Speicheranforderung existiert allerdings nur in C. In C++ stehen einem dazu wesentlich mächtigere Operatoren zur Verfügung: new und delete.
new ist das C++ Gegenstück zu malloc und delete das Gegenstück zu free. In C++ würde das gleiche Beispiel folgend aussehen:
char *str;
str = new char[15];
strcpy(str, \"Hello World\");
delete [] str;
Die Art und Weise, wie delete den Speicher wieder frei gibt, ist von Compiler zu Compiler unterschiedlich. Die beiden eckigen Klammern weisen ihn an, ein Feld zu löschen. Ohne diese Klammern ist es nicht gesichert, dass er den ganzen String löscht (abhängig vom Compiler).
|