2010-10-12 256 views
5

如果以下是可能的:混淆指針和多維數組

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

爲什麼下面是不是?

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

我發現這在啓動C編程時非常有用:http://cslibrary.stanford.edu/102/PointersAndMemory.pdf – helpermethod 2010-10-18 05:03:30

+0

查看了c-faq(http:// c- faq.com/aryptr/index.html) – pmg 2010-10-18 15:36:09

回答

10

首先,一些standard語言:

6.3.2.1左值,數組和功能代號
...
3除非是sizeof操作符或的操作數一元&運算符,或者是一個用於初始化數組的字符串,具有「數組類型」類型的表達式將轉換爲類型爲「指向類型的指針」的表達式,該表達式指向數組對象的初始元素,而不是一個左值。如果數組對象具有寄存器存儲類,則行爲未定義。

鑑於聲明

int myarray[3][3]; 

類型的myarray是 「的int 3元素數組的3元素數組」。由規則上面去,當你寫

MyFunction(myarray, 3, 3); 

表達myarray具有從「的int 3元素數組的3元素數組」到「指針3的類型隱式轉換(「衰變」)元素數組int「或int (*)[3]

因此,你的函數原型將需要

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

注意int **array一樣int (*array)[3];指針算術將會不同,所以你的下標不會指向正確的位置。請記住,數組索引被在指針運算來定義a[i] == *(a+i)a[i][j] == *(*(a + i) + j)a+i將產生不同的值,具體取決於aint **還是int (*)[N]

該特定的例子假定你總是傳遞的int一個NX3元件陣列;如果您想要處理任何NxM大小的陣列,則不會非常靈活。要解決這個問題的方法之一是明確地傳遞數組的第一個元素的地址,所以你只是傳遞一個簡單的指針,然後計算出正確的手動偏移:

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

因爲我們通過一個簡單的指針int,我們不能在MyFunc中使用雙下標; arr[i]的結果是一個整數,而不是一個指針,所以我們必須在一個下標操作中計算數組中的完整偏移量。請注意,這個技巧只適用於真正的多維數組。

現在,一個**可以指示組織在二維結構中的值,但是它是以不同方式構建的。例如:

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

去由規則如上所述,當表達式d0d1,並d2出現在初始值設定爲a,它們的類型全部從「的int 3元素數組」到「指針轉換int「。類似地,當表達a出現在調用AnotherFunc,其類型是從「指針的3元素數組int」轉換爲「指針指向int」。

請注意,在AnotherFunc我們下標兩個尺寸,而不是像我們在MyFunc中那樣計算偏移量。這是因爲a指針值的數組。表達式arr[i]得到我們第指針從位置arr的值偏移;然後我們從該指針值中找到第j個整數值偏移量。

下表可以幫助 - 它顯示了不同類型的數組表達式,它們腐爛什麼根據其聲明(T (*)[N]爲指針類型,而不是一個數組類型,因此它不會腐爛):

 
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 

高維數組的模式應該清晰。

5

編輯:這是我在更多了點答案嘗試的要求,並根據新的示例代碼:

不管陣列的尺寸,你傳遞什麼是「指向數組的指針」 - 它只是一個指針,雖然指針的類型可以改變。

在第一個例子中,int array[6]爲6個int元素的數組。傳遞array將指針傳遞到所述第一元件,這是一個int,因此參數類型是int *,其可以被等效地寫成int []

在第二個示例中,int array[3][3]是一個由3行(元素)組成的數組,每個元素包含3個int s。傳遞array將指針傳遞到所述第一元件,其是3 int小號陣列。因此該類型爲int (*)[3] - 指向由3個元素組成的數組的指針,可以等效地寫爲int [][3]

我希望你現在看到不同。當你傳遞一個int **,它實際上是一個指向int * S和指針數組二維數組。

一個例子爲一個實際int **將是這樣的:

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

這裏array是3 int *秒的陣列,以及使此作爲參數將導致int **


原來的答覆:

你的第一個例子是不是一個真正的二維數組,雖然它是在一個類似的方式使用。在那裏,你正在創建ROWS號碼的指針,每個指針指向一個不同的COLS字符數組。這裏有兩個層次的間接。

第二個和第三個示例實際上是2D數組,其中整個ROWS * COLS字符的內存是連續的。這裏只有一個間接的層次。一個指針,指向一個二維數組是不char **,但char (*)[COLS],因此可以這樣做:

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

第一個例子是可能的,因爲陣列時作爲函數參數傳遞退化爲指針。

第二個例子不起作用,因爲int[3][3]退化爲int (*)[3]雙指針int **。這是因爲二維數組在內存中是連續的,沒有這些信息,編譯器就不會知道如何訪問第一行之後的元素。考慮數字的簡單的網格:

1 2 6 
0 7 9 

如果我們在一個陣列int nums[6]存儲這些數據,如何將我們的索引到數組訪問元素7?當然是1 * 3 + 1,或者更一般的,row * num-columns + column。爲了訪問第一行之後的任何元素,您需要知道網格有多少個列。

當您將數字存儲爲nums[2][3]時,編譯器使用與您使用1D數組手動完全相同的row * num-columns + column算術,它只是對程序員隱藏。因此,在傳遞2D數組時,您必須傳遞列數以便編譯器能夠執行此算術。

在許多其他語言中,數組會攜帶有關其大小的信息,因此無需在將多維數組傳遞給函數時手動指定維度。

0

也許我們可以期待更多的「到了點」的問題,如果你想有更多的答案。你的想法有兩個問題:

  1. 2D陣列int A[3][3]在表達式中使用 時衰減到它的第一個元件的地址 從而 int (*)[3]類型的指針。爲了能夠通過你的數組必須使用&A[0][0]來獲得指向第一個「內部」成員的指針。
  2. 你的函數裏面的操作 A[i][j]不能執行,因爲 你的編譯器沒有信息 的行長在那裏。
1

其他人幾乎總結出來了。 int ** A表示A是指向數組的指針而不是對2-D數組的引用。但是,這並不意味着它不可用。由於C中的數據以行優先順序存儲,一旦知道行長,檢索數據應該很容易

0

此代碼有兩個主要問題。

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

第一個是int **array是使用的錯誤類型。這是一個指針指針,而

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

是一個多維數組。組成這個多維數組的內存都是一個塊,並且從這個數組的開始到這個數組的任何元素的偏移量都是基於這個數組中的的大小的知識來計算的。

int *A[99]; 

這是一個指向整數的指針數組。指向的整數可能是內存中幾個整數中的第一個,這意味着它們實際上指向整數數組。

在許多情況下,當您在程序中使用數組的名稱時,它會計算一個指向數組開頭的指針。如果你說:

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

你應該得到相同的地址打印3次,因爲它們都指向相同的地址,但它們的類型不一樣。後兩者的數據類型在許多用途上是相似和兼容的,因爲array[0]將被視爲指向第一行array的第一個元素的指針,並且該行本身就是一個數組。

如果你說:

int **A; 

您是說有一個指針的指針int。雖然A[2][4]是一個有效的表達,這是不是在爲同樣的方式多維數組:

int B[3][3]; 

如果說A[1]這個計算結果爲int *類似B[1]會,但你可以說A[1] = (int *)0x4444;,但如果你說B[1] = (int *)0x4444;你會得到一個編譯器錯誤,因爲B[1]實際上是一個計算值,而不是一個變量。使用B時,不存在int *變量的數組 - 只根據行大小和數組的第一個成員的地址進行一些計算。

這段代碼應該做類似於你想要的東西(爲了可讀性某些輸出格式的變化)。請注意如何更改打印語句中的索引值。

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