Grundlagen: Datentypen und Variablen
In C# gibt es, so wie in anderen Programmiersprachen auch, sogenannte Datentypen. Ein Datentyp ist ein Typ, der beschreibt, welche Art Daten oder welcher Wertebereich in einer Variablen dieses Datentyps gespeichert werden kann. Variablen werden zur Laufzeit erstellt und sind an einen Datentyp gebunden. Grundsätzlich unterscheidet man in C# zwischen Ganzzahlen, Gleitkommazahlen (auch Fließkommazahlen genannt), Flags (also ja oder nein), Zeichen (einzelnes Zeichen oder eine sogenannte Zeichenkette als Reihe mehrerer Zeichen) und Objekten.
Für Ganzzahlen sind die Datentypen byte, sbyte, short, ushort, int, uint, long und ulong. Der jeweilige Wertebereich kann dem Quellcode unten entnommen werden. Das s in sbyte steht für signed, also vorzeichenbehaftet. Das u in ushort, uint und ulong steht dagegen für unsigned, also vorzeichenlos. Bei Gleitkommazahlen stehen die Datentypen float, double und decimal zur Verfügung. Wenn wir Gleitkommazahlen im Quelltext notieren, geht der Compiler davon aus, dass es sich um den double-Datentyp handelt. Um dem Compiler mitzuteilen, dass es sich um den float- oder decimal-Datentyp handelt werden die Suffixe f oder M benötigt, welche an das Ende der Zahl notiert werden müssen.
Für Flags, gibt es in C# den Datentyp bool. bool besitzt nur zwei Zustände true (1 bzw. wahr) und false (0 bzw. unwahr). Wichtig zu wissen ist jedoch, dass die heutigen Prozessoren nicht bitweise arbeiten, sondern byteweise, d. h. beim Anlegen einer bool Variable wird immer 1 Byte Speicher benötigt. Man könnte als meinen es entspricht dem Datentyp byte. Jedoch ist der C#-Compiler so programmiert, dass es nicht möglich ist in einer bool-Variable z. B. den Wert 2 zu speichern.
Der Datentyp char kann ein Unicode-Zeichen speichern. In einigen Sprachen, wie z. B. C und C++ gibt es den Datentyp byte nicht. Hier wird einfach der Datentyp unsigned char verwendet. Dadurch entsteht der Eindruck, dass ein char ebenfalls wie der Datentyp byte 1 Byte Speicher benötigt und somit einen Wertebereich von 0 bis 255 hat. Da Windows jedoch sehr viel mit Unicode-Zeichen arbeitet, reicht ein Byte für den Datentyp char nicht aus. Deshalb entspricht der Datentyp char in C# dem Datentyp ushort. Auch hier ist der C#-Compiler so programmiert, dass er das Speichern eines Zeichens in einem ushort niemals zulassen wird (nur über einen cast, dazu später mehr). Ein einzelnes Zeichen wird in einfachen Anführungszeichen notiert. Der Datentyp string hingegen ist eine Aneinanderreihung von einzelnen Zeichen, man spricht von einer Zeichenkette. Man kann also vereinfacht sagen, dass ein string-Datentyp aus mehreren char-Datentypen besteht. Eine Zeichenkette wird in doppelten Anführungszeichen angegeben. Im Vergleich zu der Sprache C und C++ ist der string-Datentyp eine praktische Erfindung, da dieser auch jederzeit verlängert werden kann, denn in C und C++ müssen Zeichenketten mit Hilfe eines Arrays (dazu später mehr) aus char-Datentypen gebaut werden. In der Realität ist jedoch der Datentyp string nichts anderes als ein Array mit char-Datentypen, welcher jedoch automatisch verlängert werden kann. Diese Verwaltung übernimmt die Programmiersprache C# für uns.
Für alle Objekte kann der globale Datentyp object verwendet werden (dazu jedoch später mehr). In C# sind alle Datentypen an Objekte gebunden, dies ist sehr ungewöhnlich, führt jedoch andererseits zu einer sehr abstrakten und einheitlichen Struktur.
Man unterscheidet bei verschiedenen Aktionen für Variablen zwischen der Variablendeklaration, Variableninitialisierung und Wertezuweisung. Bei der Variablendeklaration legen wir die Variablen an. Dazu bestimmen wir den Datentyp und den frei wählbaren Namen (z. B. bool bFlag;). Der Name einer Variablen sollte sinnvoll gewählt werden. Des Weiteren darf dieser nur einmal im deklarierten Block vorhanden sein. Variablen die direkt innerhalb einer Klasse definiert werden, werden als Member-Variablen bezeichnet. Die Variableninitialisierung ist die erste Zuweisung einer Variablen. C# ist soweit abgefangen, dass ein Zugriff auf eine uninitialisierte Variable, vom Compiler als Fehler quittiert wird. Oftmals wird eine Variable auch direkt mit der Variablendeklaration initialisiert (wie im unteren Beispielcode). Die Wertzuweisung und die Variableninitialisierung erfolgt indem man den Variablennamen, ein Gleichheitszeichen und den Wert notiert (z. B. bFlag = false;). Einer Variablen kann mehrmals ein gleicher oder auch anderer Wert zugewiesen werden.
Nun wollen wir noch etwas genauer auf die Speicherung von Variablen eingehen. In der Programmierung gibt es den Stack-Speicher und den Heap-Speicher. Ab diesem Zeitpunkt müssen wir bei den Datentypen zwischen zwei Typen unterscheiden: Werttypen und Referenztypen. Zu den Werttypen gehören die Datentypen byte, sbyte, short, ushort, int, uint, long, ulong, float, double, decimal, bool, char und enum (dazu später mehr). Diese Datentypen werden alle im Stack-Speicher abgelegt. Zu den Referenztypen gehören die Datentypen string und object, bei welchen im Stack-Speicher lediglich eine Referenz zum Heap-Speicher abgelegt wird. Die Daten der Referenztypen liegen im Heap-Speicher. Diese Aufteilung existiert deshalb, da Datentypen vom Typ string und object u. U. einiges an Speicherplatz benötigen. Die Größe des Stack-Speichers ist jedoch begrenzt, weshalb man sich dazu entschieden hat, große Datenmengen (also Zeichenketten und Objekte) im Heap-Speicher zu speichern. Referenztypen kann der Wert null zugewiesen werden, sie sind Nullable. Der Wert null entspricht dem programmiertechnischen nichts und darf nicht mit der Zahl 0 verwechselt werden. Durch das Setzen eines Referenztyps auf den Wert null wird die Referenz gelöscht (also die Referenz auf dem Stack-Speicher). Der Garbage Collector (kurz GC) von C# kann nun prüfen, ob das Objekt noch von irgendeiner Stelle referenziert wird (also ob noch irgendeine Variable eine Referenz auf das Objekt hat). Falls das Objekt nicht mehr referenziert ist, kann das Objekt auf dem Heap-Speicher vom GC gelöscht werden. Der GC kann durch den Programmcode manuell aufgerufen werden, wird jedoch vom .NET Framework auch automatisch in unregelmäßigen Abständen (im Leerlauf oder bei Speicherengpässen) aufgerufen.
In allen Beispielen dieses und der weiteren Kapitel (abgesehen von dem aktuellen Beispiel unten) wird Ihnen auffallen, dass wir die Variablen am Anfang immer mit einem Kleinbuchstaben versehen haben (z. B. i, s, b oder o). Hierbei handelt es sich um die sogenannte Namenskonvention. Um sich im eigenen oder fremden Programm schneller zurecht zu finden, halten die meisten „besseren“ Programmierer diese Regeln ein. Die Buchstaben sind hierbei vom Datentyp abgeleitet (i für int, s für string, b für byte oder bool und o für object). Des Weiteren werden alle Variablen eines Blocks am Anfang definiert. Näheres dazu am Ende des C#-Buches.
Program.cs
byte vByte = 200; // 0 bis 255 sbyte vSByte = -45; // -128 bis 127 short vShort = -15784; // -32.768 bis 32.767 ushort vUShort = 45960; // 0 bis 65.535 int vInt = -1894112307; // -2.147.483.648 bis 2.147.483.647 uint vUInt = 3489215047; // 0 bis 4.294.967.296 long vLong = -3996794549303736183; // -9.223.372.036.854.775.808 bis 9.223.372.036.854.775.807 ulong vULong = 14125657448224163497; // 0 bis 18.446.744.073.709.551.615 float vFloat = 39751.48f; // -3.402823e38 bis 3.402823e38 double vDouble = 976252561462.7912; // -1.79769313486232e308 bis 1.79769313486232e308 decimal vDecimal = 644186892645655128968.34768426M; // +/- 1,0 × 10e?28 zu +/- 7,9 × 10e28 bool vBool = false; // true (1) oder false (0) char vChar = 'c'; // Unicode-Zeichen (0 - 65.535) string vString = "Hallo Welt!"; // Aneinanderreiung von char-Typen object vObject = new Program(); // globaler Typ für alle Objekte int vZahl; const int vZahlKonstant = 16; Console.WriteLine(vByte); Console.WriteLine(vSByte); Console.WriteLine(vShort); Console.WriteLine(vUShort); Console.WriteLine(vInt); Console.WriteLine(vUInt); Console.WriteLine(vLong); Console.WriteLine(vULong); Console.WriteLine(vFloat); Console.WriteLine(vDouble); Console.WriteLine(vDecimal); Console.WriteLine(vBool); Console.WriteLine(vChar); Console.WriteLine(vString); Console.WriteLine(vObject); Console.WriteLine(); // Variablen können zur jeder Zeit geändert werden vZahl = 418; vZahl = 9752; Console.WriteLine(vZahl); // Konstante Variablen können nicht geändert werden // vZahlKonstant = 123; --> nicht möglich, da Konstant Console.WriteLine(vZahlKonstant); Console.ReadKey();