Advanced  Services Arrays (Felder) Back Next Up Home

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 ;


Eindimensionale Felder

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 ?


Einige wichtige Punkte zu Arrays


Maximale Feldgröße und System.OutOfMemoryException

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 );
    }
}

System.OverflowException

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);

System.IndexOutOfRangeException

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 und Schleifen

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]);
}


Dynamische Feldgrößen

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] ;

    }
}

Initialisierung von Arrays

Ä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' } ;


Felder als Referenzen

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:


arrayzeiger1

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:

arrayzeiger2

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 .


Garbage Collector

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.



Arrays kopieren

Arrays zu Fuß kopieren

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] ;

Arrays kopieren mit Array.Copy()

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]) ;
   }
}

Die Hierarchie der Arrays

Arrays beliebigen Typs sind nicht mehr Werttypen, sondern bereits Referenztypen wie die folgende Graphik zeigt.

csharp-arrayhierarchie.jpg

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#.


Zwei- und mehrdimensionale Felder

Rechteckige Schemata, Matrizen

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 } };

Nicht rechteckige Schemata

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]);

Übungen

Maximum, Minimum und Mittelwert eines Arrays

Sortieren eine Feldes mit BubbleSort

Der König und seine Gefangenen

Valid XHTML 1.0 Strict top Back Next Up Home