Advanced Services | Arrays (Felder) |
Sobald man mehrere Variable des gleichen Typs braucht, kommt man mit solchen Ansätzen nicht
besonders weit:
int a, b, c, d, e, f, g, h, i ;
Leider auch nicht besser ist der folgende Versuch
int a1, a2, a3, a4, a5, a6, a7, a8 ;
Der obige zweite Versuch war im Prinzip die richtige Idee, aber eben nicht die richtige Syntax.
Wir wollen für eine gewisse Anzahl von Variablen gleichen Typs einen gemeinsamen Obernamen und
dann mit Hilfe dieses Namens und einem Index auf die einzelnen Variablen zugreifen. Stellen sie sich einen
Schubladenschrank (oder auch mehrere) in einem Büro vor. Jeder Schrank habe einen Namen und die
Schubladen sind durchnumeriert. Dann kann man sagen, holen Sie mal die Akten von Schrank c, Schublade 4 usw.
Statt Schrank sagen wir jetzt Array oder Feld oder Vektor und die Numerierung nennen wir Feldindex.
In C# sieht das folgendemaßen aus:
int[] arr ; // Vereinbaren der Feldvariable arr vom Typ int (Leeren Schrank bereitstellen)
arr = new int[17] ; // Array für 17 Variable vom Typ int einrichten (Schrank mit 17 Schubladen einrichten)
Der Operator new ist der Operator für Arrayerzeugung und später auch für
Objekterzeugung (wobei Felder schon ein Spezialfall von Objekten sind). new hat zwei Aufgaben
zu erledigen. Zum einen muß es Speicher bereitstellen und zum zweiten muß es diesen Speicher
initialisieren (siehe Felder als Referenzen) . new ist ein
unärer Operator, hat also eine sehr hohe Priorität
(siehe Tabelle der Operatoren). Er existiert nur als Prefixoperator,
d.h. sein Operand steht immer rechts von ihm. In unserem Beispiel werden durch new 17 Variable vom Typ
int angeschafft mit dem gemeinsamen Namen arr. Man kann die beiden obigen Schritte auch
in einen einzigen zusammenfassen:
int[] arr = new int[17] ; // Vereinbaren der Feldvariable arr und einrichten für 17
Variable vom Typ int
Beachten Sie, daß bei der späteren Verwendung des new-Operators (erste Variante) die Feldvariable ohne
eckige Klammern verwendet wird. Das Feld heißt eindimensional, weil wir nur einen Feldindex
verwenden. Doppeltindizierte Felder heßen zweidimensional usw. Über den Feldindex haben
wir lesenden und schreibenden Zugriff auf die einzelnen Variablen:
arr[13] = 34 ; // Schreibender Zugriff Wert 34 in die 13-te Schublade legen
int x = arr[7]; // Lesender Zugriff Wert in der 7-ten Schublade wird nach x kopiert
Console.WriteLine("Feldlänge = " + arr.length ); // Wieviele Schubladen hat der Schrank ?
Punkt 3 der obigen Auflistung hat zur Folge, daß es in C# ein Obergrenze für die
Feldgröße gibt, egal um welchen Feldtyp es sich dabei handelt. Die maximale Feldlänge
ist die größte Zahl, die im Format long darstellbar ist, also
+9 223 372 036 854 775 807 bzw. +263 -1
(siehe Tabelle der Datentypen).
Wenn man als Feldtyp byte wählt, so bräuchte man mehr als 9 Millionen Terabytes Hauptspeicher um so ein Array darzustellen.
Diese Größenordnung wird man mit einem PC so schnell nicht erreichen...
Das folgende Programm führt denn auch bereits mit der größtmöglichen int-Zahl bereits zu einer neuen Art von Fehler.
Es läßt sich problemlos compilieren, aber beim Ausführen erhält man die Fehlermeldung
System.OutOfMemoryException.
Es handelt sich hier
um einen sog. Laufzeitfehler (runtime-error), einen Fehler, der eben erst zur Laufzeit auftritt.
Im Gegensatz zu den Fehlern, die der Compiler aufdecken kann (compiletime-error).
public class TooBig { public static void Main() { int[] tooBig = new int[int.MaxValue]; // 2147483647 Console.WriteLine("tooBig.Length = " + tooBig.Length ); } }
Formal kann man zwar ein Feld mit einer long-Variablen anlegen, die einen größeren Wert hat als die maximale int-Zahl, aber zur Laufzeit ergibt sich eine OverflowException!
long len = int.MaxValue + (long)1; // = int.MaxValue Console.WriteLine("len = " + len); int[] tooBig = new int[len]; Console.WriteLine("tooBig.Length = " + tooBig.Length);
Ein beliebter Fehler sind Zugriffe auf Feldelemente, die gar nicht existieren. In C gibt es dazu weder vom Compiler noch zur Laufzeit Meldungen. Die Folge sind manchmal schwer zu lokalisierende Programmabstürze. In C# dagegen führt ein falscher Arrayzugriff zwar auch nicht zu einer Meldung des Compilers, aber der Fehler wird zur Laufzeit erkannt. Das Programm bricht mit einer Ausnahme (Exception) der Sorte IndexOutOfRangeException ab und man kann der Konsolmeldung sogar entnehmen, in welcher Zeile der falsche Arrayzugriff stattfindet.
public class TooBad { public static void Main() { int[] arr = new int[5] , i ; for( i=0; i < arr.Length ; i++ ) arr[i] = i ; Console.WriteLine(arr[i]); } }
Die Variable i hat beim Verlassen der Schleife den Wert arr.Length, also hier 5. Ein Feldelement mit dem Index 5 existiert jedoch nicht !
Felder treten eigentlich immer im Zusammenhang mit Schleifen auf. Meist verwendet man hier die for-Schleife. Das folgende Beispiel erzeugt 7 Zufallszahlen und gibt sie auf die Konsole aus:
Random rd = new Random(); double[] doubleArr = new double[7] ; for (int i = 0; i < doubleArr.Length; i++ ) { doubleArr[i] = rd.NextDouble(); } for (int i = 0; i < doubleArr.Length; i++ ) { Console.WriteLine(doubleArr[i]); }
Im Gegensatz zu C muß in C# die Feldgröße keine Konstante sein, sie kann durchaus auch von einem Benutzer bestimmt werden:
public class HowMuch { public static void Main() { int[] arr; int len ; Console.Write("Wieviele int-Variabeln brauchen Sie denn :" ); len = Convert.ToInt32( Console.ReadLine() ); if ( len > 0 && len < 100 ) arr = new int[len] ; } }
Ähnlich wie der Compiler Felder initialisiert kann man kleine Felder auch selbst initialisieren.
Dazu vereinbaren wir zuerst eine Feldvariable (Referenz) :
char[] charArr ;
Im zweiten Schritt geben wir in einem Statement die Feldinhalte durch Aufzähung der Elemente bekannt:
charArr = new char[ ] { 'a' , 'g' , 'a' , 'p' , 'i' } ;
// Hier darf new char[ ] nicht weggelassen
werden !
Offensichtlich eignet sich dieses Verfahren nur für kurze Feldlängen. Auch in diesem Fall
lassen sich beide Schritte in der Vereinbarung zusammenfassen:
char[] charArr = new char[ ] { 'a' , 'g' , 'a' , 'p' , 'i' } ;
In letzerem Fall kann man auch kürzer wie folgt schreiben:
char[] charArr = { 'a' , 'g' , 'a' , 'p' , 'i' } ;
Wie am Anfang legen wir wieder eine Feldvariable an, diesmal zur Abwechslung vom Typ double.
double[] d_arr ;
Die Variable d_arr ist ein erstes Beispiel für eine Referenz. Statt Referenz sagt man auch
Pointer oder Zeiger. Solche Variablen enthalten keine Werte, sondern Adressen von anderen Variablen. Nach
der obigen Zeile ist d_arr zunächst noch nicht mit einem sinnvollen Inhalt belegt, aber
als Speicherplatz vorhanden:
Nun legen wir ein Array mit 5 Elementen an:
d_arr = new double[5] ;
Nachdem mit new double[5] das Array angelegt worden ist, haben wir die folgende Situation:
Der new Operator hat einen zusammenhängenden Block von 5 double-Variablen angelegt und diese mit 0.0 vorbelegt. Außerdem legt er die Anfangsadresse (hier XXXX) dieses Blocks in der Referenzvariablen d_arr ab (der Wert dieser Adresse ist für uns uninteressant). Man sagt, die Referenzvariable d_arr zeigt auf den Anfang des neuen Speicherblocks. Des weiteren wird noch ein Speicherplatz Length angeschafft, der die Länge des Arrays speichert. Auf die Feldvariablen d_arr[0] bis d_arr[4] haben wir lesenden und schreibenden Zugriff, auf Length haben wir nur lesenden Zugriff oder mit anderen Worten, Length ist eine Konstante, auf d_arr haben wir weder lesenden noch schreibenden Zugriff, wir können d_arr nur verwenden, um auf die anderen Speicherplätze zuzugreifen. Diese Art des Zugriffs nennt man dereferenzieren. In diesem Fall können wir d_arr auf sechs Arten dereferenzieren und erhalten dadurch die fünf double-Variablen d_arr[0] bis d_arr[4] und die Feldlängenkonstante d_arr.Length .
Wir betrachten die folgende Situation
int[] arr ; // Feldreferenz anlegen
arr= new int[17] ; // Feld für 17 int-Variablen angelegt
// einige Zeilen Programmcode
arr= new int[34] ; // Feld für 34 int-Variablen angelegt
Obiger Programmausschnitt ist fehlerfrei und wäre in C++ eine Todsünde. Zur selben
Referenz wird ein neuer Speicher für ein neues Array angefordert. Die alten Feldelemente sind nicht
mehr ansprechbar und hängen im Speicher. Der Programmierer hat vergessen, den nicht mehr
benützten Speicher freizugeben. In Java dürfen wir uns so eine "Schlamperei" erlauben.
Zur Laufzeit tritt nämlich ein Müllschlucker (garbage collector) in Aktion, der immer wieder
den Speicher nach nicht mehr verwendeten (nicht mehr ansprechbaren) Bereichen durchforstet und
diese dann freigibt. Der garbage collector arbeitet als Hintergrundprozeß mit niedriger
Priorität. Er tritt immer wieder in Aktion, aber man kann als Programmierer nicht bestimmen, wann
er seine Arbeit beginnt. Es sind keine zeitlichen Vorhersagen möglich. Man kann lediglich durch
die Zuweisung
arr = null ;
dem garbage collector signalisieren, daß eine Referenz nicht mehr gebraucht wird. Trotzdem bestimmt
er den Zeitpunkt für die Entsorgung. Wir haben keinen Einfluß darauf.
Natürlich kann man immer mit einer for-Schleife arbeiten.
short[] arr = new short[20] ; short[] arr2 = new short[20] ; for(int i=0; i < arr.Length; i++) arr[i] = i*i ; for(int i=0; i < arr2.Length; i++) arr2[i] = arr[i] ;
Mit Hilfe der Klasse Array geht das in einer Zeile. Hier ein kleines Beispielprogramm.
public class ArrayCopy { public static void Main() { int sourceArr = new int[] { 1, 2, 3, 4, 5, 6, 7 }; int[] destArr = new int[7]; // muß initialisiert werden ! Array.Copy(sourceArr, destArr, intArr.Length); Console.WriteLine("---- die kopie ----"); for (int i = 0; i < intArr.Length; i++) Console.WriteLine(destArr[i]) ; } }
Arrays beliebigen Typs sind nicht mehr Werttypen, sondern bereits Referenztypen wie die folgende Graphik zeigt.
Die Klasse Array ist also nicht nur eine Hilfsklasse, die nützliche Methoden zum Bearbeiten von Arrays bereitstellt.
Mindstens ebenso wichtig ist ihre Bedeutung als Basisklasse aller Arraytypen in C#.
Für rechteckige Schemata gibt es eine vereinfachte Syntax.
int[,] zweidim = new int[4, 3]; // 4 Zeilen, 3 Spalten Console.WriteLine(zweidim.Length); // 12 Gesamtzahl der Elemente Console.WriteLine(zweidim.GetLength(0)); // 4 Anzahl der Zzeilen Console.WriteLine(zweidim.GetLength(1)); // 3 Anzahl der Spalten
Zum Belegen mit Werten und zum Auslesen braucht man geschachtelte Schleifen.
for (int i = 0; i < zweidim.GetLength(0); i++) { for (int j = 0; j < zweidim.GetLength(1); j++) zweidim[i, j] = i + j; }
Ausgeben:
for (int i = 0; i < zweidim.GetLength(0); i++) { for (int j = 0; j < zweidim.GetLength(1); j++) Console.Write(zweidim[i, j] + " "); Console.WriteLine(); }
Für kleine Felder bietet sich eine kürzere Variante an.
int[,] zweidim2 = { { 0, 1, 2, 3, 4 }, { 5, 6, 7, 8, 9 } };
Das sind zweidimensionale Felder, bei denen die einzelnen Zeilen unterschiedliche Längen haben. Für diese Fälle muß man eine andere Syntax wählen. Für die Unterfelder ist hier new int[] notwendig.
int[][] zweidim = { new int[] { 0, 1, 2, 3 }, new int[] { 4, 5, 6 }, new int[] { 7, 8 }, new int[] { 9 } , new int[] {} }; // OK
Hier kann man nicht mit der Methode GetLength() arbeiten, genauer gesagt, diese methode existiert nur zum Index 0 und liefert wie Length die Anzahl der Unterarrays.
Console.WriteLine(zweidim.Length); // 5 Anzahl der UnterArrays Console.WriteLine(zweidim.GetLength(0)); // 5
Die Ausgabe der Elemente sieht in diesem Fall wie folgt aus.
for (int i = 0; i < zweidim.Length; i++) { for (int j = 0; j < zweidim[i].Length; j++) Console.Write(zweidim[i][j] + " "); Console.WriteLine(); }
Nebenbei sieht man, daß es auch Arrays der Länge 0 gibt.
Eine andere Möglichkeit der Initialisierung ist die folgende.
int[][] zweidim2 = new int[5][]; zweidim2[0] = new int[] { 0, 1, 2, 3 }; zweidim2[1] = new int[] { 4,5,6 }; zweidim2[2] = new int[] { 7,8 };
Hier noch ein Beispiel eines dreidimensionalen Feldes.
int[][][] dreidim = new int[3][][]; dreidim[0] = new int[2][]; dreidim[1] = new int[3][]; dreidim[2] = new int[2][]; dreidim[0][0] = new int[] { 1, 2 }; dreidim[0][1] = new int[] { 3, 4, 5 }; dreidim[1][0] = new int[] { 6, 7, 8 }; dreidim[1][1] = new int[] { 9, 8 }; dreidim[1][2] = new int[] { 7, 6 }; dreidim[2][0] = new int[] { 5, 4 }; dreidim[2][1] = new int[] { 3, 2, 1 };
Ausgabe des Feldelementes, das die 9 enthält:
Console.WriteLine(dreidim[1][1][0]);
Maximum, Minimum und Mittelwert eines Arrays
Sortieren eine Feldes mit BubbleSort
Der König und seine Gefangenen