2010-10-12 12 views
5

Wenn die folgende möglich:Verwirrung über Zeiger und mehrdimensionale Arrays

MyFunction(int *array, int size) 
{ 
    for(int i=0 ; i<size ; i++) 
    { 
     printf(“%d”, array[i]); 
    } 
} 

main() 
{ 
    int array[6] = {0, 1, 2, 3, 4, 5}; 
    MyFunction(array, 6); 
} 

Warum ist die folgende nicht?

MyFunction(int **array, int row, int col) 
{ 
    for(int i=0 ; i<row ; i++) 
    { 
     for(int j=0 ; j<col ; j++) 
     { 
      printf(“%d”, array[i][j]); 
     } 
    } 
} 

main() 
{ 
    int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; 
    MyFunction(array, 3, 3); 
} 
+0

Ich fand dies sehr hilfreich beim Starten von C-Programmierung: http://cslibrary.stanford.edu/102/PointersAndMemory.pdf – helpermethod

+0

werfen Sie einen Blick auf den Array vs Zeiger Abschnitt der C-FAQ (http: // c- faq.com/aryptr/index.html) – pmg

Antwort

10

Zunächst einige standard Sprache:

6.3.2.1 Lvalues, Arrays und Funktion Bezeich
...
3 Außer wenn es der Operand des sizeof Operator oder unary & operator, oder ist ein String-Literal, das zum Initialisieren eines Arrays verwendet wird, wird ein Ausdruck vom Typ "array of type" in einen Ausdruck mit dem Typ "pointer to type" konvertiert, der auf das ursprüngliche Element des Array-Objekts zeigt ein Wert. Wenn das Array-Objekt über eine Regis- terspeicherklasse verfügt, ist das Verhalten nicht definiert.

die Deklaration

Gegeben
int myarray[3][3]; 

der Typ von myarray "3-Element-Array von 3-Element-Array von int" ist.Geht durch die Regel oben, wenn man

MyFunction(myarray, 3, 3); 

der Ausdruckmyarray seinen Typen zu schreiben implizit konvertiert hat ("decay") von "3-Element-Array von 3-Element-Array von int" auf „Zeiger auf 3 -Elementarray von int "oder int (*)[3].

So würde Ihre Funktionsprototyp

int MyFunction(int (*array)[3], int row, int col) 

Beachten Sie, dass int **array ist nicht die gleiche wie int (*array)[3] sein müssen; Die Zeigerarithmetik wird anders sein, sodass Ihre Indizes nicht auf die richtigen Stellen zeigen. Denken Sie daran, dass Array-Indexierung in Bezug auf Zeigerarithmetik definiert ist: a[i] == *(a+i), a[i][j] == *(*(a + i) + j). a+i ergibt einen anderen Wert abhängig davon, ob a ein int ** oder ein int (*)[N] ist.

In diesem speziellen Beispiel wird davon ausgegangen, dass Sie immer ein Nx3-Element-Array von int; nicht besonders flexibel, wenn Sie mit einem NxM-großen Array arbeiten möchten. Eine Möglichkeit, dies zu umgehen wäre ausdrücklich passieren die Adresse des ersten Elements in der Anordnung, so dass Sie vorbei nur einen einfachen Zeiger, und dann berechnet die richtigen manuell versetzt:

void MyFunction(int *arr, int row, int col) 
{ 
    int i, j; 
    for (i = 0; i < row; i++) 
    for (j = 0; j < col; j++) 
     printf("%d", a[i*col+j]); 
} 

int main(void) 
{ 
    int myarray[3][3] = {{1,2,3},{4,5,6},{7,8,9}}; 
    ... 
    MyFunction(&myarray[0][0], 3, 3); 

Da wir übergeben Sie einen einfachen Zeiger auf int, können wir nicht einen doppelten Index in MyFunc verwenden; Das Ergebnis von arr[i] ist ein Integer, kein Zeiger, also müssen wir den vollständigen Offset in das Array in der Ein-Index-Operation berechnen. Beachten Sie, dass dieser Trick nur für wirklich mehrdimensionale Arrays funktioniert.

nun ein **kann zeigen Werte, die in einer 2-D-Struktur organisiert sind, aber eine, die eine andere Art und Weise gebaut wurde. Zum Beispiel:

void AnotherFunc(int **arr, int row, int col) 
{ 
    int i, j; 
    for (i = 0; i < row; i++) 
    for (j = 0; j < col; j++) 
     printf("%d", arr[i][j]); 
} 

int main(void) 
{ 
    int d0[3] = {1, 2, 3}; 
    int d1[3] = {4, 5, 6}; 
    int d2[3] = {7, 8, 9}; 

    int *a[3] = {d0, d1, d2}; 

    AnotherFunc(a, 3, 3); 
    ... 
} 

Going durch die Regel oben, wenn die Ausdrücke d0, d1 und d2 in dem Initialisierer für a erscheinen, sind alle Arten von „3-Element-Array von int“ auf „Zeiger auf umgewandelt int ". Wenn der Ausdruck a in dem Aufruf von AnotherFunc angezeigt wird, wird sein Typ in ähnlicher Weise von "3-Elementarray des Zeigers zu int" in "Zeiger auf Zeiger auf int" konvertiert.

Beachten Sie, dass wir in AnotherFunc beide Dimensionen indizieren, anstatt den Offset wie in MyFunc zu berechnen. Das liegt daran, a ist ein Array von Zeiger Werte. Der Ausdruck arr[i] bekommt uns die i'th Zeiger Wert Offset von der Position arr; Wir finden dann den j-ten ganzzahligen Wert-Offset von diesem Zeigerwert.

Die folgende Tabelle helfen könnte - es zeigt die Typen verschiedener Array Ausdrücke und was sie zerfallen zu auf der Grundlage ihrer Erklärungen (T (*)[N] ist ein Zeigertyp, kein Array-Typ, so dass es nicht zerfallen ist):

 
Declaration   Expression   Type   Implicitly Converted (Decays) to 
-----------   ----------   ----   -------------------------------- 
    T a[N]      a   T [N]   T * 
           &a   T (*)[N] 
           *a   T 
          a[i]   T 

    T a[M][N]      a   T [M][N]  T (*)[N] 
           &a   T (*)[M][N] 
           *a   T [N]   T * 
          a[i]   T [N]   T * 
          &a[i]   T (*)[N] 
          *a[i]   T 
          a[i][j]   T 

T a[L][M][N]     a   T [L][M][N]  T (*)[M][N] 
           &a   T (*)[L][M][N] 
           *a   T [M][N]  T (*)[N] 
          a[i]   T [M][N]  T (*)[N] 
          &a[i]   T (*)[M][N] 
          *a[i]   T [N]   T * 
          a[i][j]   T [N]   T * 
         &a[i][j]   T (*)[N] 
         *a[i][j]   T 
         a[i][j][k]   T 

Das Muster für höherdimensionale Arrays sollte klar sein.

5

Edit: Hier ist mein Versuch einer mehr den Punkt Antwort wie auf Ihrem neuen Beispielcode angefordert und basiert:

Unabhängig von der Array-Dimensionen, was Sie übergeben, ist ein "Zeiger auf ein Array" - es ist nur ein einzelner Zeiger, obwohl der Typ des Zeigers variieren kann. In Ihrem ersten Beispiel ist int array[6] ein Array von 6 int Elementen. Übergeben array übergibt einen Zeiger auf das erste Element, das eine int ist, daher der Parametertyp ist int *, die gleichwertig wie int [] geschrieben werden kann.

In Ihrem zweiten Beispiel ist int array[3][3] ein Array von 3 Zeilen (Elementen) mit jeweils 3 int s. Passing array übergibt einen Zeiger auf das erste Element, das ist ein Array von 3 int s. Daher ist der Typ int (*)[3] - ein Zeiger auf ein Array von 3 Elementen, die äquivalent geschrieben werden können als int [][3].

Ich hoffe, Sie sehen den Unterschied jetzt. Wenn Sie eine int ** übergeben, ist es tatsächlich ein Zeiger auf ein Array von int * s und NICHT ein Zeiger auf ein 2D-Array.

Ein Beispiel für eine tatsächliche int ** wäre so etwas wie dieses:

int a[3] = { 1, 2, 3 }; 
int b[3] = { 4, 5, 6 }; 
int c[3] = { 7, 8, 9 }; 
int *array[3] = { a, b, c }; 

Hier array ist ein Array von 3 int * s, und vorbei dies als Argument in einem int ** führen würde.


Ursprüngliche Antwort:

Ihr erstes Beispiel ist nicht wirklich eine 2D-Array, obwohl es in ähnlicher Weise verwendet wird. Dort erstellen Sie ROWS Nummer von char * Zeigern, von denen jeder auf ein anderes Array von COLS Zeichen zeigt. Es gibt zwei Ebenen der Indirektion.

Das zweite und dritte Beispiel sind eigentlich 2D-Arrays, bei denen der Speicher für die gesamten ROWS * COLS Zeichen zusammenhängend ist. Es gibt nur eine Ebene der Indirektion.Ein Zeiger auf eine 2D-Array ist nicht char **, aber char (*)[COLS], so können Sie dies tun:

char (*p)[SIZE] = arr; 
// use p like arr, eg. p[1][2] 
0

Das erste Beispiel ist möglich, weil Arrays Zeiger degenerieren, wenn sie als Funktionsparameter übergeben.

Das zweite Beispiel funktioniert nicht, weil int[3][3]-int (*)[3] degeneriert, nicht ein Doppelzeiger int **. Dies ist der Fall, weil 2D-Arrays zusammenhängend im Speicher sind, und ohne diese Information würde der Compiler nicht wissen, wie auf Elemente nach der ersten Zeile zuzugreifen ist. Betrachten wir ein einfaches Gitter von Zahlen:

1 2 6 
0 7 9 

Wenn man diese Zahlen in einem Array int nums[6] Speicherung wurden, wie würden wir Index in das Array 7 das Element zugreifen? Durch 1 * 3 + 1, natürlich oder allgemeiner, row * num-columns + column. Um auf ein Element nach der ersten Zeile zuzugreifen, müssen Sie wissen, wie viele Spalten das Gitter hat.

Wenn Sie die Zahlen als nums[2][3] speichern, verwendet der Compiler die gleiche row * num-columns + column Arithmetik wie Sie manuell mit einem 1D-Array, es ist nur vom Programmierer ausgeblendet. Daher müssen Sie beim Übergeben eines 2D-Arrays die Anzahl der Spalten übergeben, damit der Compiler diese Arithmetik ausführen kann.

In vielen anderen Sprachen enthalten Arrays Informationen über ihre Größe, so dass Dimensionen beim Übergeben mehrdimensionaler Arrays an Funktionen nicht manuell angegeben werden müssen.

0

Vielleicht können wir eine "auf den Punkt" Frage erwarten, wenn Sie eine mehr auf den Punkt bringen wollen. Ihre Idee hat zwei Probleme:

  1. eine 2D-Array int A[3][3] verwendet, wenn in einem Ausdruck abklingt zur Adresse seines ersten Elements so zu einen Zeiger des Typs int (*)[3]. Um das Array durchzulassen, müssen Sie &A[0][0] verwenden, um einen Zeiger auf das erste "innere" Element zu erhalten.
  2. Innerhalb Ihrer Funktion kann die Operation A[i][j] nicht durchgeführt werden, da Ihr Compiler hat keine Informationen die Zeilenlänge, dort.
1

Die anderen haben es ziemlich summiert. int ** A bedeutet, dass A ein Zeiger auf ein Array und kein Verweis auf ein 2-D-Array ist. Das bedeutet jedoch nicht, dass es nicht verwendbar ist. Da die Daten in C in Reihenreihenfolge gespeichert sind, sollte die Datenerfassung nach der Kenntnis der Zeilenlänge einfach sein.

0

Es gibt zwei Hauptprobleme mit diesem Code.

MyFunction(int **array, int row, int col); 

Die erste ist, dass ist der falsche Typ zu verwenden. Dies ist ein Zeiger auf einen Zeiger, während

ein mehrdimensionales Array ist. Der Speicher, aus dem dieses mehrdimensionale Array besteht, besteht aus einem einzigen Chunk, und der Versatz vom Anfang dieses Elements zu einem beliebigen Element dieses Arrays wird basierend auf der Kenntnis der Größe einer Zeile in diesem Array berechnet.

int *A[99]; 

Dies ist ein Array von Zeigern auf Ganzzahlen. Die Ganzzahlen, auf die verwiesen wird, könnten die erste von mehreren Ganzzahlen im Speicher sein, was bedeutet, dass diese tatsächlich auf ganze Zahlen verweisen.

Unter vielen Umständen, wenn Sie den Namen eines Arrays in einem Programm verwenden, wird ein Zeiger auf den Anfang des Arrays ausgewertet. Wenn Sie sagen:

int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; 
printf("%p %p %p\n", array, array[0], &(array[0][0])); 

sollten Sie die gleiche Adresse erhalten 3 mal gedruckt, weil sie alle auf die gleiche Adresse verweisen, aber ihre Typen sind nicht das gleiche. Der Datentyp der letzten beiden ist ähnlich und für viele Zwecke kompatibel, da array[0] als ein Zeiger auf das erste Element der ersten Zeile von array behandelt werden würde und diese Zeile ein Array für sich ist.

Wenn Sie sagen:

int **A; 

Sie sagen, dass es einen Zeiger auf einen Zeiger auf ein int ist. Während A[2][4] ein gültiger Ausdruck ist, dann ist dies wie in der gleichen Art und Weise nicht ein mehrdimensionales Array:

int B[3][3]; 

Wenn Sie A[1] sagen, dass dies zu einem wertet int * ähnlich wie B[1] würde, mit der Ausnahme, dass Sie A[1] = (int *)0x4444; sagen kann, aber wenn man sagte B[1] = (int *)0x4444; Sie würden einen Compilerfehler erhalten, weil B[1] ist eigentlich ein berechneter Wert, keine Variable. Mit B gibt es kein Array von int * Variablen - nur einige Berechnungen basierend auf der Zeilengröße und der Adresse des allerersten Elements des Arrays.

Dieser Code sollte etwas tun, was dem entspricht, was Sie wollten (einige Formatierungsänderungen für die Lesbarkeit). Beachten Sie, wie der Indexwert in der print-Anweisung geändert wird.

MyFunction(int *array, int row, int col) 
{ 
    int x = 0; 
    for(int i=0 ; i<row ; i++) 
    { 
     for(int j=0 ; j<col ; j++) 
     { 
      printf(“%d ”, array[x++]); 
     } 
     printf("\n"); 
    } 
} 

main() 
{ 
    int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; 
    MyFunction(array, 3, 3); 
}