Hmm, irgendwie kommt es immer zu Problemen, wenn die Leute anfangen, in C/C++ mit Zeigern und Arrays zu arbeiten.
Der erste Fehler ist immer, dass sie glauben, ein Array wäre etwas anderes als ein Zeiger. Das ist aber nicht so.
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
|
void foo ()
{
int array[10];
int* pointer = new int [10];
...
delete[] pointer;
}
|
In diesem Fall ist
array einer Zeiger auf den Stack, wo der Compiler automatisch Platz für 10 Integer angelegt hat, der bei verlassen der Funktion automatisch wieder freigegeben wird (verlassen in dem Sinn, dass die Funktion komplett durchgelaufen ist).
pointer ist ein Zeiger auf den Heap, wo man von Hand Platz für 10 Integer angelegt hat, den man auch selbst wieder freigeben muss (nicht notwendig in der selben Funktion). Ein Array ist einfach ein zusammenhängender Speicherbereich, in dem nacheinander die Elemente des Array stehen. Der Zeiger beschreibt die Speicherstelle, an der das erste Element steht.
Der nächste Fehler ist die Annahme, ein Zeiger wäre irgendwie etwas anderes, als ein char, int oder float. Das ist nicht so, es handelt sich bei einem Zeiger auch um ein Objekt, das Platz im Speicher verbraucht und, was noch wichtiger ist, der irgendwo im Speicher steht; damit kann es einen Zeiger auf die Stelle geben, wo wieder ein Zeiger steht (was hier gerne als
Doppelpointer :roll: bezeichnet wird).
Man stelle sich ein Buch vor, die Seiten sind der Speicher, die Seitenzahlen sind die Zeiger. Wenn auf Seite 1 ein Wort steht ist die 1 der Zeiger auf das Wort. Ein Array wären dann die Seiten 5 bis 7, repräsentiert durch die erste Seite des Array, also 5. Ab Seite 10 befindet sich der Index. Dort stehen selbst wieder Seitenzahlen, also zeigt der Zeiger 10 wieder auf Objekte, die eben selbst auch wieder Zeiger sind, mit denen ich dann auf die richtige Seite gelange. Das kann man beliebig so fortsetzen.
Was hat es nun mit 2D Arrays auf sich? Hier bieten sich zwei Optionen, wie man das realisieren kann.
Option 1: Ich kann ein 2D Array wie ein 1D Array interpretieren. Für ein Array mit 3 Zeilen à 5 Elemente bedeutet das, ich habe 15 Seiten in meinem Buch, die ersten 5 Seiten gehören zu Zeile 1, die nächsten zu Zeile 2 und die letzen zu Zeile 3. Der Vorteil ist, ich muss nur einmal Speicher anfordern; der Nachteil ist, ich muss selbst ausrechnen, ab welcher Seite eine Zeile beginnt.
Option 2: Ich benutze einen Index für die Zeilen, also muss ich für mein 3 Zeilen, 5 Spalten Array einen Index mit 3 Seiten haben, auf denen jeweils die Seite steht, an der eine Zeile beginnt. Für jede Zeile brauche ich wieder 5 Seiten, insgesamt also 18 Seiten. Vorteil ist, ich muss nicht selbst Rechnen, wo eine Zeile beginnt, Nachteil ist ich muss selbst sicherstellen, dass für jede Zeile genügend Seiten angefordert werden.
Eigentlich gibt es noch eine dritte Option, als Erweiterung für Option 1. Ich kann mir nämlich auch einmal einen Index berechnen, in dem ich speichere, wo meine Zeilen beginnen und brauche doch nicht selbst rechnen.
Das Prinzip lässt sich beliebig für mehr Dimensionen erweitern, egal welche Option man benutzt.
So, und nun zur eigentlichen Frage:
|
C-/C++-Quelltext
|
1
|
int array2d[3][5];
|
In diesem Fall überlasse ich dem Compiler die Entscheidung welche Option benutzt wird und erkaufe mir damit, dass der Compiler für mich die relevanten Berechnungen und Speicheranforderungen durchführt. Wenn ich das Konstrukt also an eine Funktion übergeben will, muss ich dem Compiler auch vollständig mitteilen, wie er den Zugriff zu interpretieren hat.
|
C-/C++-Quelltext
|
1
|
void foo (int array2d_arg[3][5]);
|
Wenn man Option 1 oder Option 2 benutzt, sieht das wieder ein wenig anders aus.
|
C-/C++-Quelltext
|
1
2
|
void foo_opt1 (int* array_anfang, int zeilen = 3, int spalten = 5);
void foo_opt2 (int** array_index, int zeilen = 3, int spalten = 5);
|
In beiden Fällen reiche ich die Größe des Array mit in die Funktion, damit ich dort weiss, wie gross das Array tatsächlich ist, und damit ich ggf. auch richtig umrechnen kann (für Option 1).
Version foo_opt1 bekommt einen Zeiger auf den Anfang des Speicherbereichs übergeben, an dem die Daten liegen und muss dann selbst umrechnen, wo ein konkretes Element (x, y) im Array liegt (gewöhlich rechnet man x + (y * spalten)).
Version foo_opt2 bekommt nur den Zeiger auf den Index übergeben und kann über diesen Index dann auch die Zeiger auf das jeweils erste Element der Zeilen bestimmen.
In allen Fällen wird kein einziges Element des Array kopiert, sondern es wird nur die Adresse übergeben, wo die richtigen Informationen zu finden sind. Der Unterschied liegt nur darin, wer und wie man diese Informationen hinterher interpretiert.
So, das ist jetzt eine Menge Text, aber ich hoffe, dass doch noch jemand was daraus lernen kann.
Gruss,
Rainer