Ein mehrdimensionales Array ist ein Array, dessen Elemente selbst Arrays sind. int grid[3][4]; ist kein besonderer Rastertyp - es sind drei Elemente, von denen jedes ein Array aus vier ints ist, hintereinander abgelegt. Sobald das klickt, folgt der Rest des Themas: das Layout, die Indexarithmetik und die sonst rätselhafte Regel zum Übergeben an Funktionen.
Zwei Dimensionen decken fast jede praktische Verwendung ab - Raster, Tabellen, Matrizen, Spielbretter, Bilder -, und damit arbeitet diese Seite.
Deklarieren und initialisieren
int grid[3][4]; // 3 Zeilen, 4 Spalten - 12 ints
Die erste Zahl ist die Anzahl der Zeilen, die zweite die der Spalten. Initialisierer lassen sich flach oder mit inneren geschweiften Klammern schreiben; die Klammern lohnen sich, weil sie die Form zeigen.
Die Form int e[][3] ist wichtig: Du darfst die Zeilenzahl leer lassen und den Initialisierer entscheiden lassen, aber die Spaltenzahl ist nie optional. Der nächste Abschnitt erklärt, warum.
Zeilenweises Layout
C speichert ein 2D-Array in zeilenweiser Anordnung: die ganze Zeile 0, dann die ganze Zeile 1 und so weiter, in einem ununterbrochenen Speicherblock. Hinter den Kulissen gibt es kein Array von Zeilenzeigern.
int grid[3][4] = {{ 1, 2, 3, 4},
{ 5, 6, 7, 8},
{ 9,10,11,12}};
wie du es dir vorstellst wie es wirklich im Speicher liegt
+----+----+----+----+
| 1 | 2 | 3 | 4 | Zeile 0 +--+--+--+--+--+--+--+--+--+--+--+--+
+----+----+----+----+ | 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|
| 5 | 6 | 7 | 8 | Zeile 1 +--+--+--+--+--+--+--+--+--+--+--+--+
+----+----+----+----+ \_ Zeile 0 _/\_ Zeile 1 _/\_Zeile 2_/
| 9 | 10 | 11 | 12 | Zeile 2
+----+----+----+----+ grid[i][j] liegt bei Index i*4 + j
Diese Formel, i * Spalten + j, ist der gesamte Mechanismus - und deshalb muss der Compiler die Spaltenzahl kennen, um überhaupt zu indizieren. Die Zeilenzahl geht nie in die Rechnung ein.
Du kannst das Layout direkt sehen, indem du Adressen ausgibst:
Die Adressen steigen um sizeof(int) ohne Lücken, auch dort, wo eine Zeile endet und die nächste beginnt. Die flache Schleife beweist es - flat[k] läuft alle zwölf Elemente als einen einzigen Lauf ab.
Dieses Layout hat auch eine wissenswerte Leistungsfolge: Zeilen-dann-Spalten zu durchlaufen berührt den Speicher der Reihe nach, und das mag der CPU-Cache. Die Verschachtelung zu tauschen, sodass die innere Schleife eine Spalte hinabsteigt, springt stattdessen jedes Mal um eine ganze Zeile und kann bei einem großen Array mehrfach langsamer laufen.
Verschachtelte Schleifen
Zwei Dimensionen wollen zwei for-Schleifen: Die äußere wählt die Zeile, die innere durchläuft deren Spalten.
Benenne die Zähler nach dem, was sie bedeuten (i/row für Zeilen, j/col für Spalten), und halte die Reihenfolge einheitlich - überall grid[row][col]. Die Hälfte aller 2D-Array-Bugs sind ein vertauschtes Indexpaar.
Auch die per #define festgelegten Größen sind keine Zierde: Die Schleifengrenzen und die Deklaration können jetzt nicht mehr auseinanderdriften, wenn du die Form änderst.
Ein 2D-Array an eine Funktion übergeben
Hier ist die Regel, über die jeder stolpert: Der Parameter der Funktion muss die Spaltenzahl angeben.
Der Grund ist der Zerfall. grid zu übergeben wandelt es in einen Zeiger auf sein erstes Element um - und seine Elemente sind Zeilen, der Typ ist also int (*)[4]: Zeiger auf ein Array von 4 ints. Damit grid[i][j] überhaupt etwas bedeutet, muss der Compiler wissen, wie weit eine Zeile reicht, und das ist die 4. Die Zeilenzahl fehlt wirklich im Typ, weshalb sie als eigenes Argument mitreist.
Beachte, dass int (*grid)[COLS] und int grid[][COLS] derselbe Parameter in zwei Schreibweisen sind - die Klammern sind Pflicht, denn int *grid[COLS] wäre stattdessen ein Array von Zeigern. Diese Unterscheidung behandelt Zeiger und Arrays.
Wenn die Spaltenzahl erst zur Laufzeit bekannt ist, erlauben C99s variabel modifizierte Parameter, sie zuerst zu übergeben:
void print_any(int rows, int cols, int grid[rows][cols]);
rows und cols müssen vor dem Array-Parameter deklariert sein, der sie benutzt. Wo das nicht verfügbar ist, ist die übliche Alternative ein flaches 1D-Array plus eigene Indexarithmetik:
data[i * cols + j] ist genau das, was der Compiler im Fall fester Größe für dich schreibt. Es von Hand zu tun kostet eine Zeile und funktioniert für jede zur Laufzeit bestimmte Form.
Ein Matrixbeispiel
Die Matrixmultiplikation bringt die ganze Seite zusammen - drei verschachtelte Schleifen über zeilenweise Speicherung.
Zwei nachahmenswerte Einzelheiten. Die innere k-Schleife paart a[i][k] mit b[k][j] - ein Index läuft eine Zeile ab, der andere eine Spalte. Und das Transponieren beginnt seine innere Schleife bei j = i + 1: Bei 0 zu beginnen würde jedes Paar zweimal tauschen und die Matrix unverändert lassen.
Drei Dimensionen und mehr
Das Muster setzt sich fort, und die Regel zu Funktionsparametern auch - jede Dimension außer der ersten muss angegeben werden.
In der Praxis ist bei drei Dimensionen die Stelle, an der Arrays fester Größe unhandlich wirken, und der meiste Code wechselt zu einem flachen Block mit berechneten Indizes oder zu einem Array von Strukturen, das benennt, was jede Achse bedeutet.
Häufige Fehler
grid[i, j]schreiben. Der Komma-Operator wertetiaus, verwirft es und indiziert mitj. Es kompiliert. Es ist falsch. Nimmgrid[i][j].- Die Indizes vertauschen.
grid[col][row]liest ein echtes Element von der falschen Stelle, es gibt also keinen Fehler, der es abfängt. Behalte überall die Reihenfolge[row][col]. - Die Spaltengröße in einem Parameter weglassen.
void f(int grid[][])kompiliert nicht, und das ist der Compiler, der dich rettet. - Über die Grenzen hinausgehen. Wie bei jedem Array gibt es keine Bereichsprüfung.
grid[0][5]auf einem[3][4]-Raster liest stillgrid[1][1], weil das Layout zusammenhängend ist und die Arithmetik sich nicht darum schert.
Häufig gestellte Fragen
Wie deklariert man ein 2D-Array in C?
Gib zwei Größen in eckigen Klammern an: int grid[3][4]; deklariert 3 Zeilen zu 4 Spalten - insgesamt 12 ints. Lies es als „ein Array aus 3 Dingen, von denen jedes ein Array aus 4 ints ist", denn genau so speichert C es.
Wie wird ein 2D-Array in C im Speicher abgelegt?
In zeilenweiser Anordnung: alle Elemente von Zeile 0, dann alle von Zeile 1 und so weiter, in einem zusammenhängenden Block. grid[i][j] liegt i * Spalten + j Elemente hinter dem Anfang, weshalb die Spaltenzahl die Zahl ist, die der Compiler braucht.
Wie übergibt man ein 2D-Array in C an eine Funktion?
Der Parameter muss die Spaltenzahl angeben: void print(int grid[][4], int rows) oder gleichwertig void print(int (*grid)[4], int rows). Die Zeilenzahl darf entfallen, weil das Array zu einem Zeiger auf eine Zeile zerfällt - aber ohne die Spaltengröße kann der Compiler nicht berechnen, wo eine Zeile beginnt.
Kann man ein 2D-Array mit lauter Nullen initialisieren?
Ja: int grid[3][4] = {0}; nullt jedes Element, denn jedes nicht aufgeführte Element wird mit null initialisiert. int grid[3][4] = {{1, 2}}; setzt die ersten beiden Einträge von Zeile 0 und lässt die anderen zehn auf null.