Arbeiten mit .NET: Materialien/ Programmierkurs C-Sharp: Datentypen Details

DatentypenBearbeiten

C# kennt zwei Arten von Datentypen: Wertdatentypen und Referenzdatentypen.

Wertdatentypen enthalten die Daten direkt, wobei Referenzdatentypen im Gegensatz dazu nur Referenzen auf die eigentlichen Daten darstellen. Wertdatentypen können in C# auch als Referenzdatentypen verwendet werden durch das sogenannte Boxing, dazu jedoch mehr im Kapitel über Typumwandlungen (Casts).

GrundlagenBearbeiten

DeklarationBearbeiten

Bei der Deklaration einer Variablen wird ihr Name und ihr Typ festgelegt. Dabei wird bei Wertdatentypen der benötigte Speicher reserviert, bei Referenzedatentypen der Speicher für den Verweis reserviert, der später auf den eigentlichen Datenbereich des Objektes verweist. Die Zuweisung eines Wertes bzw. einer Referenz kann während der Deklaration erfolgen oder später.

Wird der Deklaration const hinzugefügt, dann wird damit eine Konstante deklariert.

Der Zusatz readonly gibt an, dass sie nur einmal zugewiesen werden können. Danach ist nur noch lesender Zugriff erlaubt.

Wir haben bereits bei den grundlegenden Kontrollstrukturen Variablen deklariert, hier nun die allgemeine Syntax. Die Deklaration erfolgt durch Angabe eines Datentyps gefolgt von einem Variablennamen:

 Datentyp Variablen;

 Variablen := Variablenname [,Variablen]

Ein Variablenname darf kein Schlüsselwort sein, er muss mit einem Buchstaben oder einem _ beginnen, darf danach allerdings auch Ziffern enthalten. Prinzipiell darf er fast jedes Unicode-Zeichen enthalten, das einen Buchstaben darstellt, aber mit deutschen Sonderzeichen (ü) sollte man dennoch Vorsicht walten lassen. Wenn man den Quellcode weitergeben möchte, stellt man Menschen aus anderen Kulturkreisen vor Probleme, denn sie haben diese Zeichen nicht auf der Tastatur.

Ein einfache Variablendeklaration haben wir bereits kennengelernt:

int i;

Es können auch mehrere Variablen des gleichen Typs innerhalb einer Deklaration zusammengefasst werden:

 int i, j, k;

Variablen muss vor der ersten Verwendung ein Wert zugewiesen werden. Üblicherweise passiert dies bei der Deklaration, es kann allerdings auch hinterher getan werden:

 int i;
 int j = 2, k = 3;
 i = j;

Auch die Mehrfachzuweisung eines Wertes an verschiedene Variablen ist möglich:

 int i, j, k;
 i = j = k = 123;

GültigkeitsbereicheBearbeiten

Variablen haben fest definierte Gültigkeitsbereiche. Hier werden vorerst nur die Gültigkeitsbedingungen innerhalb von Funktionen erklärt, da für andere Variablen weitere Begriffe (Klassen, Objekt etc.) erst noch geklärt werden müssen.

Im Allgemeinen gilt, dass Variablen, die in einem Block deklariert werden, nicht außerhalb des Blockes gültig sind. Sobald der Block verlassen wird, wird der reservierte Speicher wieder freigegeben. Das klingt jetzt sehr theoretisch, daher ein Beispiel.

using System;
public class Gueltigkeitsbereiche
{
	public static void Main()
	{
		int aussen = 0;
		for(int i = 0; i<10; i++)
		{
			int innen = 0;
			//Erlaubt, die Variable innen ist innerhalb dieses Blocks deklariert
			innen++;
			//Erlaubt, die Variable aussen ist in einem Äußeren Block deklariert
			aussen++;
			Console.WriteLine("Innen: "+innen);
			Console.WriteLine("Außen: "+aussen);
		}
		//int innen = 0;
		//Kompilerfehler, die Variable innen wird außerhalb des Deklarationsblocks verwendet
		Console.WriteLine("Innen: "+innen);
		//Erlaubt, die Variable aussen ist innerhalb dieses Blocks deklariert
		Console.WriteLine("Außen: "+aussen);
	}
}

Dieses Beispiel lässt sich so nicht kompilieren, da die Variable innen außerhalb ihres Deklarationblockes benutzt wird, der Kompiler gibt bei der Fehlermeldung sogar aus, dass ihm die Variable unbekannt sei. Dies liegt daran, dass sie hier wieder deklariert werden kann. Dies erreicht man in dem man //int innen = 0; wieder einkommentiert. Im Allgemeinen sollte man aber mit so etwas vorsichtig sein. Es zeugt nicht gerade von Stil, den selben Variablennamen zwei mal so knapp hintereinander zu deklarieren. Der Code wird unleserlich, außerdem ist dies eine gute Möglichkeit nur schwer auffindbare Fehler zu produzieren. Variablennamen sollten immer eindeutig und aussagekräftig sein.

BasisdatentypenBearbeiten

BoolBearbeiten

Den Datentyp bool haben wir ja bereits kennengelernt. Es ist die abgekürzte schreibweise für Boolean, welche im .NET durch den Struct System.Boolean definiert ist. Er kann zwei Werte annehmen true (Wahr) und false (Falsch). Sein Name kommt nicht von irgendwoher, er ist nach dem Mathematiker George Boole, dem Vater der booleschen Algebra, benannt.

Logische OperationenBearbeiten

Für den Datentyp bool gibt es 3 logische Operationen: not(!), and(&&) und or(||).

Beim not-Operator wird der Wert invertiert, d.h. ist etwas nicht wahr ist es falsch und umgekehrt.
Der and-Operator verknüpft zwei Werte miteinander. Er ergibt true wenn beide Eingangswerte true sind, andernfalls false.
Der or-Operator verknüpft ebenfalls zwei Werte miteinander. Er ergibt true wenn einer der Eingangswerte true ist und nur dann false wenn beide Eingangswerte false sind.

Dies war sehr kurz, von daher alle Fälle als Programmierbeispiel:

using System;
public class BoolBeispiel
{
	public static void Main()
	{ 

		Console.WriteLine("not(!) Operrator");
		Console.WriteLine("!true ist: " + !true);
		Console.WriteLine("!false ist: " + !false);

		Console.WriteLine();
		Console.WriteLine("and(&&) Operrator");

		Console.WriteLine("true && true ist: " + (true && true));
		Console.WriteLine("true && false ist: " +(true && false));
		Console.WriteLine("false && true ist: " +(false && true));
		Console.WriteLine("false && false ist: " +(false && false));

		Console.WriteLine();
		Console.WriteLine("or(||) Operrator");

		Console.WriteLine("true || true ist: " + (true || true));
		Console.WriteLine("true || false ist: " +(true || false));
		Console.WriteLine("false || true ist: " +(false || true));
		Console.WriteLine("false || false ist: " +(false || false));
		
		Console.ReadLine();
	}
}

Beim Ausführen dieses Beispiels kann man sehr schnell erkennen, was diese 3 Operanten bewirken. Ähnlich wie in der "normalen" Mathematik gibt es auch hier eine Punkt-vor-Strich-Regel. Hier gilt immer, dass der and-Operator den Vorrang hat. Es ist allerdings möglich dies durch setzen von Klammern zu umgehen:

using System;
public class BoolKlammer
{
	public static void Main()
	{  
		bool first = true || true && false;
		Console.WriteLine("First: " +first);
		bool second = true && false || true;
		Console.WriteLine("Second: " +second);
		bool third = (true || true) && false;
		Console.WriteLine("Third: " +third);
		Console.ReadLine();
	}
}

Die ersten beiden Verknüpfungen sind bei der Ausführung identisch, da hier das and zuerst ausgeführt wird. Durch die Klammer wurde dies in der dritten Verknüpfung ausgehebelt.

Bit-OperationenBearbeiten

Beim Datentyp bool sind 3 bit-Operatoren vorhanden: and (&) or (|) und xor (^). Zwei kommen uns schon bekannt vor. Diese bitweise and und or verhalten sich auch ähnlich, auch die and-vor-or-Regel gilt hier. Es gibt allerdings zu den logischen Pendants einen Unterschied. Bei den logischen Operationen wird die Abarbeitung abgebrochen wenn bereits klar ist, welchen Wert das Ergebnis haben wird, weil die nachfolgenden Operationen den Wert nicht mehr verändern können. Wenn z. B. ein Wert schon true ist, kann ein nachfolgendes or nichts daran ändern. Hierzu ein Beispiel:

using System;
public class BoolBit
{
	public static void  Main()
	{
		Console.WriteLine("Erst das logische OR");
		//ReturnFalse wird nicht aufgerufen, da der Wert insgesamt nur true sein kann
		bool logisch = true || ReturnFalse();
		Console.WriteLine("Jetzt die Bitoperation mit OR");
		//ReturnFalse wird aufgerufen, da Bitoperationen immer aufgerufen werden
		bool bit = true | ReturnFalse();
		Console.ReadLine();
	}
	public static bool ReturnFalse()
	{
		Console.WriteLine("Dieser Text ist bei dem logischen OR nicht zu sehen");
		return false;
	}
}

Anmerkung: Mit public static bool ReturnFalse() wird eine neue Funktion deklariert. In diese wird gesprungen wenn sie aufgerufen wird. Mehr dazu später.

Anhand dieses Beispiels kann man den Unterschied erkennen. Bei den Logischen and wird ReturnFalse() nicht aufgerufen, da es egal ist ob der Aufruf false oder true ergeben würde, das Ergebnis bleibt gleich. Bei den Bitweisen and wird der Aufruf allerdings ausgeführt.

Das xor(^) steht für exklusives oder. Es ergibt dann true, wenn die beiden Eingangsparameter unterschiedlich sind, anderen falls false. Auch hier soll ein Programmierbeispiel zur Verdeutlichung dienen.

using System;
public class BoolXor
{
        public static void  Main()
        {
                Console.WriteLine("true ^ true ist: " + (true ^ true));
                Console.WriteLine("true ^ false ist: " + (true ^ false));
                Console.WriteLine("false ^ true ist: " + (false ^ true));
                Console.WriteLine("false ^ false ist: " + (false ^ false));
                Console.ReadLine();
        }
}

Eigentlich kann man das xor mit and und or Nachbilden, was man in vielen anderen Programmiersprachen auch machen muss. Die verkürzte Schreibweise ist allerdings viel leserlicher:

using System;
public class BoolXor
{
        public static void Main()
        {
                bool a = true;
                bool b = true;
                bool xor1 = a^b;
                //Xor ausführlich geschrieben
                bool xor2 = (a||b)&&!(a&&b);
                //sind die beiden xor Varianten verschieden?
                Console.Writeline("xor1: "+xor1);
                Console.Writeline("xor2: "+xor2);
                Console.ReadLine();
        }
}

Es sei dem Leser überlassen hier alle Varianten auszuprobieren. Allerdings sollte er sich überlegen, wieso (a||b)&&!(a&&b); das xor durch logische Operationen nachbildet.

GanzzahlenBearbeiten

Ganzzahlen sind ebenfalls wichtige Basisdatentypen. Mit ihnen kann man Berechnungen mit ganzen Zahlen bewerkstelligen, das heißt mit Zahlen ohne Kommastellen.

Wichtig dabei ist, dass es die Ganzzahlen in 4 verschiedenen Längen gibt: 1Byte, 2Byte, 4Byte, 8Byte. Es gibt für jede Länge jeweils einen Datentyp mit Vorzeichen und einen ohne Vorzeichen.

Interne DarstellungBearbeiten

GrundlagenBearbeiten

Um diese Datentypen und ihr Verhalten verstehen zu können muss man ihren Aufbau kennen. Dies soll an den Beispielen byte und sbyte erfolgen. Sie haben jeweils die Länge, wie der Name schon vermuten lässt, von einem Byte, was 8 Bit entspricht. 1 Bit ist mit einem Boolean vergleichbar, wobei hier eine 1 für true steht, eine 0 für false. Ganzzahlen stellen daher die Zahlen nicht wie wir in der Basis 10 sondern in der Basis 2 dar. Die Bits werden von hinten nach vorne ab 0 durchgezählt. Bei Vorzeichenlosen Ganzzahlen stellt ein Bit an der Stelle n den Wert   dar:

Allgemeine Darstellung
Bitnummer 7 6 5 4 3 2 1 0
Wertigkeit Dezimal 128 64 32 16 8 4 2 1
Zahl 65 als Byte( 64 + 1 )
Bits 0 1 0 0 0 0 0 1
Dezimal 0 64 0 0 0 0 0 1
Zahl 136 als Byte( 128 + 8 )
Bits 1 0 0 0 1 0 0 0
Dezimal 128 0 0 0 8 0 0 0
AdditionBearbeiten

Die Addition folgt den gleichen Rechenregeln, welche auch in der 10ner Basis gelten, es werden immer 2 Ziffern addiert. Wird dabei der Wert der Basis überstiegen gibt es einen Übertrag. 0+0 = 0, 1+0 = 1, 1+1 = 0 mit 1 Übertrag.

1 + 1 = 2
1 0 0 0 0 0 0 0 1
+1 0 0 0 0 0 0 0 1
Übertrag - - - - - - 1 -
2 0 0 0 0 0 0 1 0
3 + 3 = 6
3 0 0 0 0 0 0 1 1
+3 0 0 0 0 0 0 1 1
Übertrag - - - - - 1 1 -
6 0 0 0 0 0 1 1 0
50 + 103 = 153
50 0 0 1 1 0 0 1 0
+103 0 1 1 0 0 1 1 1
Übertrag 1 1 - - 1 1 - -
153 1 0 0 1 1 0 0 1
255 +1 = 0 !
255 - 1 1 1 1 1 1 1 1
+1 - 0 0 0 0 0 0 0 1
Übertrag 1 1 1 1 1 1 1 1 -
0 1 0 0 0 0 0 0 0 0

Was passiert jetzt, wenn alle Bits auf 1 setzt (der Maximalwert 255) und 1 hinzuzählt? Rechts ist der Fall gezeigt. Der aufmerksame Leser wird bemerkt haben, dass eigentlich 256 das Ergebnis sein müsste. Es wird ja auch das neunte Bit (  = 256) dafür dargestellt. Dies ist richtig, doch erinnern wir uns. Das Byte hat nur 8 Bit, auch nach der Addition, von daher ist der Inhalt des Ergebnisses 0. Es ist ein sogenannter Overflow passiert. Da er die Grundlage vieler nur schwer auffindbarer Bugs ist, stellt C# als erste Hochsprache ein Programmierkonstrukt (checked) bereit um Operrationen auf Wunsch dagegen abzusichern. Wir werden es später kennen lernen.

SubtraktionBearbeiten

Wir haben gerade die Addition kennengelernt, aber auch die Subtraktion folgt denselben Regeln der bekannten 10ner Basis, bei welcher Ziffern subtrahiert werden, und sich von einer höheren Stelle bei Bedarf etwas geliehen wird (Übertrag). 0-0 = 0; 1-0=1; 0-1=1 mit Übertrag 1, 1-1 = 0

2 - 1 = 1
2 0 0 0 0 0 0 1 0
-1 0 0 0 0 0 0 0 1
Übertrag - - - - - - 1 -
1 0 0 0 0 0 0 0 1
5 - 2 = 3
5 0 0 0 0 0 1 0 1
-2 0 0 0 0 0 0 1 0
Übertrag - - - - - 1 - -
3 0 0 0 0 0 0 1 1
99 - 11 = 88
99 0 1 1 0 0 0 1 1
- 11 0 0 0 0 1 0 1 1
Übertrag - - 1 1 - - - -
88 0 1 0 1 1 0 0 0
Negative ZahlenBearbeiten

Da wir jetzt Subtrahieren können, wäre es auch gut negative Zahlen darstellen zu können, der Datentyp byte kann dies allerdings nicht. Doch schon bereits in der Einführung wurde erwähnt, dass es für jede Länge einen Datentyp gibt, welcher auch negative Zahlen darstellen kann. Bei der Länge von einem Byte ist dies sbyte. Die Vorzeichen behafteten Ganzzahlen werden im sogenannten Zweierkompliment dargestellt, was bedeutet das vorderste Bit für das Vorzeichen reserviert wird (1 steht für -, 0 für +). Die Zahlen 0 bis 127 werden dargestellt wie bisher, höhere Zahlen können allerdings nicht dargestellt werden, was auch logisch ist, denn es stehen ja nur 7 Bit zur Darstellung zur Verfügung. Bei den Negativen Zahlen wird die Darstellung von der bisherigen geändert. So bedeutet z. B. 1000 0001 (Binär) nicht -1, sondern -127. Dies liegt darin begründet, das sich Prozessoren so einfacher bauen lassen, was die Kosten senkt. Das war zu der Zeit, in der dieses System erfunden wurde (zu den Anfängen des Computer) ein absolutes K.O. Kriterium. Es können nämlich für vorzeichenbehaftete und vorzeichenlose Datentypen die gleichen Schaltungen behalten werden, weil sich die Rechenregeln nicht ändern, sondern nur die Interpretation des Inhalts. Dies soll an einigen Beispielen erläutert werden:

0 - 1 = -1
0 0 0 0 0 0 0 0 0
- 1 0 0 0 0 0 0 0 1
Übertrag 1 1 1 1 1 1 1 -
-1 1 1 1 1 1 1 1 1
127 + 1 = -128!
127 0 1 1 1 1 1 1 1
+1 0 0 0 0 0 0 0 1
Übertrag 1 1 1 1 1 1 1 -
-128 1 0 0 0 0 0 0 0
99 + (-11) = 88
99 0 1 1 0 0 0 1 1
+ (-11) 1 1 1 1 0 1 0 1
Übertrag 1 1 - - 1 1 1 -
88 0 1 0 1 1 0 0 0

Im Beispiel 2 wird wieder ein Overflow gezeigt, da die positive Zahl 128 nicht dargestellt werden kann. Die Darstellung negativer Zahlen im Zweierkomplement ist nicht so einfach fürs menschliche Auge zu lesen, doch es gibt einen einfachen Trick, wie man von der negativen Zahl zur positiven kommt und umgekehrt. Man erreicht dies, in dem man alle Bits kippt (aus 1 wird 0 aus 0 wird 1) und dann 1 hinzuzählt. Es existieren allerdings 2 Zahlen die auf sich selbst verweisen und zwar die 0 (was richtig ist -0 = 0) und die -128, was logisch ist, fehlt doch die positive 128 (eine Darstellung muss für die 0 reserviert sein).

Zahl 1 zu -1 wandeln
1 0 0 0 0 0 0 0 1
Kippen 1 1 1 1 1 1 1 0
+1 ergibt -1 1 1 1 1 1 1 1 1
0 bleibt 0
0 0 0 0 0 0 0 0 0
Kippen 1 1 1 1 1 1 1 1
+1 ergibt 0 0 0 0 0 0 0 0 0
Zahl -23 zu 23 wandeln
-23 1 1 1 0 1 0 0 1
Kippen 0 0 0 1 0 1 1 0
+1 ergibt 23 0 0 0 1 0 1 1 1
AnmerkungenBearbeiten

Datentyp Bits Vorz. .NET struct
byte 8 N System.Byte
sbyte 8 J System.SByte
short 16 J System.Int16
ushort 16 N System.UInt16
int 32 J System.Int32
uint 32 N System.UInt32
long 64 J System.Int64
ulong 64 N System.UInt64

An dieser Stelle sei die Einführung in die binäre Darstellung von Ganzzahlen zu Ende, da diese Grundlagen reichen sollten um alles Folgende zu verstehen. Es wäre aber durchaus ratsam bei Gelegenheit sich tiefer in dieses Thema einzuarbeiten (wie funktionieren z. B. Multiplikation und Division?). Allerdings ist noch eines anzumerken:

Natürlich kann man in C# mit größeren Zahlen als 256 rechnen. Dies wird in der Regel auch getan. Dazu stehen Ganzzahldatentypen mit höher Bitanzahl zur Verfügung. byte und sbyte wurden hier nur zur Veranschaulichung genommen, da sie den kleinsten Wertebereich haben. Es gilt im Allgemeinen bei allen vorzeichenbehafteten Datentypen können   positive und   negative Werte dargestellt werden, bei vorzeichenlosen   positve Werte zuzuüglich der 0, wobei n für die Anzahl der bits (8* Anzahl Bytes) steht. Der Datentyp der am häufigsten verwendet wird ist int (32 bit, vorzeichenbehaftet).

Alle Ganzzahlen im Überblick sind rechts zu sehen.

Logische OperationenBearbeiten

GrundrechenartenBearbeiten

Für Ganzzahlen sind folgende logische Operationen definiert: + (Plus), - (Minus), * (Mal), / (Geteilt), % (Modulo). Die ersten 3 Verhalten sich, wie man es auch aus der Mathematik kennt, mit der Ausnahme von Overflows. Beim geteilt Operrator ist darauf zu achten, dass das Ergebnis ebenfalls vom Eingangstyp ist. Das heißt das Ergebnis ist ebenfalls eine Ganzzahl. 3/2 ergibt also 1 nicht 1,5. Um den Rest der einer Division zu bestimmen steht der Modulooperrator bereit. 3%2 ergibt 1. So kann man die Originalzahl wieder berechnen: 7/3 = 2 Rest 1. 3*2+1 = 7. Wie in der Mathematik gilt auch in C# die Punkt vor Strich Regel, welche man mit setzen von Klammern umgehen kann.

using System;
public class Ganzzahloperratoren
{
	public static void  Main()
	{
		int ergebnis = 5*6+1;
		Console.WriteLine("5*6+1=" +ergebnis);
		ergebnis = 5*(6+1);
		Console.WriteLine("5*(6+1)=" +ergebnis);
		ergebnis = 244/3;
		int rest = 244%3;
		Console.WriteLine("244/3="+ergebnis+" Rest:"+rest);
		ergebnis = ergebnis*3+rest;
		Console.WriteLine("ergebnis*3+rest="+ergebnis);
		Console.ReadLine();
	}
}
Verkürzte SchreibweisenBearbeiten

Es gibt in C# noch einige Möglichkeiten einige Operationen verkürzt zu schreiben, falls nur der Inhalt einer Variablen modifiziert werden soll. So kann variable = variable +3; auch durch variable+=3 ausgedrückt werden. Diese Verkürzung funktioniert bei allen Typen, bei denen der entsprechende Operator definiert ist. Da Increment (+1) und Decrement (-1) in der Programmierung oft gebraucht werden, z. B. bei einer Forschleife, gibt es für sie auch besondere Schreibweisen: ++ und --. Wobei es einen Unterschied gibt, ob diese vor oder nach der Variable geschrieben wird. Bei ++variable wird erst incrementiert und dann andere Operationen im Ausdruck ausgeführt. Bei variable++ wird erst der andere Ausdruck ausgeführt und dann incrementiert. Console.WriteLine(++variable) ist also mit variable+=1;Console.WriteLine(variable); equivalent,Console.WriteLine(variable++) mit Console.WriteLine(variable);variable+=1;. Hierzu ein kleines Beispiel:

using System;
public class Ganzzahloperratoren
{
	public static void  Main()
	{
		//Variable die mit ++ davor incrementiert wird
		int ppi = 0;
		//Variable die mit ++ danach incrementiert wird
		int ipp = 0;
		//hier wird i mit i+=1 incrementiert, die Verkürzte Schreibweise für i=i+1;
		for(int i = 0; i<3; i+=1)
		{
			Console.WriteLine("++ vor Variable: "+(++ppi));
			Console.WriteLine("++ nach Variable: "+(ipp++));
			Console.WriteLine("Hier sind beide dennoch gleich: "+(ipp==ppi));
		}
		Console.ReadLine();
	}
}

Dieses Beispiel kann man analog auch für den -- Operator vorstellen. Es sei dem Leser überlassen dies zu schreiben.

Overflow-ErkennungBearbeiten

Nun, wir haben bereits bei der Darstellung der Ganzzahldatentypen gelernt, dass Overflows passieren können, dort wurde erwähnt das es in C# ein Schlüsselwort (checked) gibt, mit welchem sich Overflows erkennen lassen. Tritt in einem solchen kontrollierten Bereich ein Overflow auf, wird eine sogenannte Exception (Ausnahmefehler) ausgelöst. Innerhalb eines checked-Bereiches kann man weiterhin Bereiche wieder mit unchecked in einen "unkontrollierten" verwandeln:

using System;
public class Ganzzahloperratoren
{
	public static void  Main()
	{
		//Variable die mit ++ davor incrementiert wird
		int max = int.MaxValue;
		checked
		{
			unchecked
			{
				Console.WriteLine("Hier werden Overflows ohne Probleme geschehen: "+(max+1));
			}
			//Exception, hier passiert ein Overflow in einem geschützten Bereich
			Console.WriteLine("Hier lösen Overflows eine Exception aus: "+(max+1));
		}
	}
}

Anmerkung: Mit int max = int.MaxValue; wird max der maximal darstellbare Wert des Datentypes int zugewiesen.

Dieses Programm gibt die erste Zeile noch aus, danach stürzt das Programm mit einer OverflowException ab. Natürlich kann man solche auftretenden Exceptions auch behandeln, so das dass Programm wie geplant weiterläuft. Dazu werden wir aber erst später mehr lernen. Es sollte noch angemerkt werden, das man mit dem checked vorsichtig umgehen sollte. + - * werden im Allgemeinen als trusted ( vertrauenswürdig) angesehen. D.h. sie sollten keine Exception werfen. Von daher sollte er nur gezielt verwendet werden. Beim / ist dies etwas anders. Zwar gilt sie auch als trusted allerdings gibt es hier eine Ausnahme: Die Division durch 0. In den meisten Programmiersprachen ist ihr Ergebnis undefiniert. In C# tritt immer eine DivideByZeroException auf, egal ob in einem checked oder unchecked Bereich:

using System;
public class Ganzzahloperratoren
{
	public static void  Main()
	{
		int i = 0;
		//Fehler, es tritt zur Laufzeit eine DivideByZeroException auf
		int j = 5/i;
		Console.ReadLine();
	}
}

Bit OperationenBearbeiten

Für Ganzzahldatentypen sind in C# folgende Operrationen definiert: << (Right Shift) >> (Left Shift) & (Bitweises and) | (Bitweises Or) ^ (Bitweises Xor). Diese sind aber nur für 32 und 64 bit Ganzzahlen definiert. Wieso dies so ist, weiß wohl nur Microsoft allein, schlüssig ist es jedenfalls nicht. Bei manchen der folgenden Beispielen werden die Datentypen byte und sbyte verwendet werden, dies geschieht nur um die Übersicht zu waren, da sie eine geringe Bitanzahl haben.

and, or und xorBearbeiten

Beginnen wollen wir mit den Bitweisen and, Bitweisen or und Bitweisen xor. Diese drei werden häufig beim Einsatz von von sogenannten Bitmasken verwendet. Bei ihnen werden jeweils die Bits mit der selben Wertigkeit (also an der selben Stelle) logisch Verknüpft und an die entsprechende die Stelle mit der selben Wertigkeit ins Ergebnis geschrieben.

Bitweises and (&)
1. Operrant 0 0 0 0 1 0 1 1
2. Operrant 1 0 1 0 1 0 0 1
Ergebnis 0 0 0 0 1 0 0 1
Bitweises or (|)
1. Operrant 0 0 0 0 1 0 1 1
2. Operrant 1 0 1 0 1 0 0 1
Ergebnis 1 0 1 0 1 0 1 1
Bitweises xor (^)
1. Operrant 0 0 0 0 1 0 1 1
2. Operrant 1 0 1 0 1 0 0 1
Ergebnis 1 0 1 0 0 0 1 0

Es wurde hier mit Absicht auf die Angabe der dezimalen Interpretation der Daten verzichtet, denn die Operationen werten den Inhalt nicht logisch aus. Ihnen ist es egal ob die Zahlen im Zweierkomplement dargestellt werden oder nicht. Nach dem wir nun geklärt haben was diese Operationen tun, wollen wir zeigen für was sie verwendet werden können. Man könnte sich z. B. folgendes Szenario vorstellen:

Studenten bekommen von ihrer FH oder Uni eine Matrikelnummer. Diese Nummer muss eindeutig sein, so dass jeder Student nur eine Matrikelnummer besitzt, und jede Matrikelnummer nur einen Studenten, damit man nur anhand der Matrikelnummer weitere Informationen (z. B. Namen) aus einer Datenbank holen kann. Des weiteren soll in der Matrikelnummer vermerkt sein, in welchem Studiengang der Student studiert.

Diese Anforderung kann man z. B. mit einem uint realisieren. Die hinteren 2 Bytes werden für die Angabe des Studiengangs genommen, die vorderen 2 Bytes für eine eindeutige Nummer des Studenten innerhalb des Studiengangs. Dies unterstützt 65536 Studiengänge mit jeweils 65.536 Studenten. Man kann diese Zahlen dann durch Bitmasken verarbeiten oder zusammensetzen.

Wir nehmen an die Uni bietet den Studiengang BWL (Studiengangnummer 0) und den Studiengang Informatik (Studiengangnummer 1) an. Man kann jetzt aus einer Matrikelnummer mithilfe des Bitweisen and auf Studiengang und auf die eindeutige Nummer des Studenten innerhalb des Studiengangs schließen.

Von der Matrikelnr. des Studenten auf den Studiengang schließen
Matrikelnummer (2818049) 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
& Bitmaske für Studiengang 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Nummer des Studiengangs 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
Von der Matrikelnr. des Studenten Nummer Innerhalb des Studiengangs schließen
Matrikelnummer (2818049) 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
& Bitmaske für Nr. im Studiengang 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Nummer im Studiengang 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Der Student mit der Matrikelnummer 2818049 studiert also Informatik (Studiengangnummer 1) und hat in diesem Studiengang die Nummer 43. Hätte man diese Informationen einzeln, so könnte man die Matrikelnummer mit dem Bitweisen or zusammensetzen.

Aus Nummer innerhalb des Studiengangs
Nummer des Studiengangs (1) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
| Nummer des Studenten innerhalb des Studiengangs (43) 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Matrikelnummer (2818049) 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1

Das Ganze jetzt als Programmierbeispiel:

using System;
public class BitMasken
{
	public static void Main()
	{
		uint matrikelNummer = 2818049;
		//Maske für Studiengang, entspricht 00000000 00000000 11111111 11111111 Binär
		uint studiengangMaske = 65535;
		//Maske für Nummer innerhalb des Studiengangs entspricht 11111111 11111111 00000000 00000000 Binär
		uint nummerMaske = 4294901760;
		//Den Studiengang herausfinden
		uint studiengang = matrikelNummer&studiengangMaske;
		//Die Nummer inerhalb des Studiengangs herrausfinden
		uint nummer = matrikelNummer&nummerMaske;
		nummer >>= 16;
		//Die Ergebnisse ausgeben
		Console.WriteLine("Studiengang hat die Nummer: " +studiengang);
		Console.WriteLine("Student hat innerhalb des Studiengangs die Nummer: " +nummer);
		//Matrikelnummer zusammensetzen
		nummer <<= 16;
		uint matrikelNummer2 = studiengang|nummer;
		Console.WriteLine("Zusammengesetzt ergibt sich die Matrikelnummer: " +matrikelNummer2);
		Console.ReadLine();
	}
}

Anmerkung: Mit nummer>>=16; wird der Inhalt der Variable um 16 nach rechts geschiftet, mit nummer<<=16; wieder zurück nach links. Wir werden dieses sogenannte Bitshifting gleich noch näher kennen lernen.

Das Bitweise xor wird man seltener brauchen, aber auch mit ihm lassen sich manche Dinge schön lösen. Stellen wir uns einmal vor, wir wollen von einem int mit beliebigen Wert den Absolutbetrag, also den Mathematischen Betrag des Wertes erfahren, so langt es ja nicht bei negativen Zahlen das Vorzeichenbit mit dem and zu löschen. Man muss alle Bits kippen und dann eins hinzuzählen. Mit einem bitweisen xor einer Zahl mit -1 werden alle Bits gekippt, da bei der -1 im binären alle Bits 1 (true) sind:

using System;
public class Abs
{
	public static void Main()
	{
		int number = -33;
		int absNumber;
		if(number<0)
		{
			absNumber = number^-1;
			absNumber++;
		}
		else
		{
			absNumber=number;
		}
		Console.WriteLine("Der Absolutbetrag von "+number +"ist "+absNumber);
		Console.ReadLine();
	}
}

Anmerkung:Natürlich muss man in C# keinen Absolutbetrag selbst schreiben, dafür steht die Funktion Math.Abs() bereit. Und ja dies ließe sich auch ohne xor realisieren in dem man absNumber = -number; schreiben würde.

Diese Funktion gibt immer den Absolutbetrag der Zahl an, welcher number am Anfang zugewiesen wird, mit einer Ausnahme: int.MinValue, diese Verhält sich genau so wie bereits erwähnt: Sie verweist auf sich selbst.

Es gibt noch eine weitere Möglichkeit das xor kreativ zu benutzen. Man kann mit ihm den Inhalt zweier Variablen tauschen, ohne eine dritte temporäre Variable zu verwenden.

using System;
public class Swap
{
	public static void Main()
	{
		int a = 999;
		int b = -1;
		Console.WriteLine("Am Anfang hat a den Wert: "+a);
		Console.WriteLine("Am Anfang hat b den Wert: "+b);
		Console.WriteLine();
		//Swap (Inhaltstausch) mit temporären Variablen
		int temp = a;
		a = b;
		b = temp;
		Console.WriteLine("Nach dem ersten Swap hat a den Wert: "+a);
		Console.WriteLine("Nach dem ersten Swap hat b den Wert: "+b);
		Console.WriteLine();
		//Swap (Inhaltstausch) mit xor ohne temporäre Variable
		a = a^b;
		b = b^a;
		a = a^b;
		Console.WriteLine("Nach dem zweiten Swap hat a wieder den Wert: "+a);
		Console.WriteLine("Nach dem zweiten Swap hat b wieder den Wert: "+b);
		Console.ReadLine();
	}
}
BitshiftingBearbeiten

Bei den sogenannten Bitshifts werden die Bits jeweils um eins nach Rechts (Rightshift >>) oder Links (Leftshift <<) verschoben. Dabei geht jeweils das Bit am Rand verloren und in die Lücke wird eine 0 gesetzt. Die einzige Ausnahme ist, wenn die Zahl negativ ist und nach Rechts geschoben wird. Dann wird die entstehende Lücke mit 1 aufgefüllt:

 
 
 

Der Rightshift um eine Stelle teilt damit einen Operanden durch 2, der Ausnahme von -1, bei welchem das Ergebnis wieder -1 ergibt.

Zur Wiederholung nun ein Programm, welches eine Ganzzahl Binär an der Konsole ausgibt. Zur Festigung des Wissen, sollte man sich die einzelnen Schritte in der Binärdarstellung darstellen.

using System;
public class BinaerDarstellung
{
	public static void Main()
	{
		int eingabe = 99;
		int temp = eingabe;
		int mask =1;
		string binaer = "";
		for(int i = 0; i<32; i++)
		{
			if((mask&temp)==1)
			{
				binaer = "1"+binaer;
			}
			else
			{
				binaer = "0"+binaer;
			}
			temp>>=1;
		}
		Console.WriteLine(eingabe +" ist binär: "+binaer);
		Console.ReadLine();
	}
}

Vergleichende OperationenBearbeiten

In C# sind für Ganzzahlen mehrere vergleichende Operationen bekannt: > (Größer), >= (Größer Gleich), < (Kleiner), <= (Kleiner Gleich), == (Gleich) und != (Ungleich), ihr Ergebnis ist immer vom Typ bool, da sie nur Wahr oder Falsch sein können.

Prüfen auf GleichheitBearbeiten

In C# kann man Ganzzahlen auf Gleichheit bzw. Ungleichheit prüfen. Diese Operationen sind für alle Datentypen definiert und werden hier formal eingeführt. Mit dem == Operateration kann man bei Ganzzahlen nachprüfen ob ihr Inhalt gleich sind, mit != kann man nachprüfen ob ihr Inhalt unterschiedlich ist. Wir haben mit diesen Operationen bereits gearbeitet, daher nur ein kleines Beispiel:

using System;
public class Gleichheit
{
	public static void Main()
	{
		int i1 = 99;
		int i2 = 100;
		int i3 = 99;
		if(i1 ==i2)
		{
			Console.WriteLine("i1 und i2 sind gleich");
		}
		else
		{
		   Console.WriteLine("i1 und i2 sind unterschiedlich");
		}
		if(i2!=i3)
		{
			Console.WriteLine("i2 und i3 sind unterschiedlich");
		}
		else
		{
			Console.WriteLine("i2 und i3 sind gleich");
		}
		Console.ReadLine();
	}
}
Prüfen von RelationenBearbeiten

Es gibt ebenfalls die Möglichkeit zu prüfen, in wie weit zwei Zahlen relativ zu einander stehen, dies kann z. B. beim sortieren wichtig sein, um berechnete Ergebnisse zu prüfen. Auch sie haben wir bereits teilweise kennengelernt, z. B. bei der for Schleife. Sie sollten leicht zu verstehen sein, von daher auch hier nur ein kleines Beispiel:

using System;
public class Relationen
{
	public static void Main()
	{
		int i1 = 99;
		int i2 = 100;
		if(i1<i2)
		{
			Console.WriteLine("i1 ist kleiner als i2");
		}
		if (i1>i2)
		{
			Console.WriteLine("i1 ist größer als i2");
		}
		if(i1<=i2)
		{
			Console.WriteLine("i1 ist kleiner oder gleich i2");
		}
		if(i1>=i2)
		{
			Console.WriteLine("i1 ist größer oder gleich  i2");
		}
		Console.ReadLine();
	}
}

FließkommazahlenBearbeiten

Datentyp Bit Präzision Wertebereich Hüllenklasse (struct type)
float 32 7 Stellen   System.Single
double 64 15-16 Stellen   System.Double

ZeichendatentypenBearbeiten