[英]Confusion about pointers and multidimensional 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);
}
為什么以下不是?
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);
}
首先,一些標准語言:
6.3.2.1左值,數組和函數指示符
...
3除非它是sizeof運算符或一元&運算符的操作數,或者是用於初始化數組的字符串文字,否則類型為“ array of type”的表達式將轉換為類型為“ pointer to type”的表達式指向數組對象的初始元素,並且不是左值。 如果數組對象具有寄存器存儲類,則該行為是不確定的。
給出聲明
int myarray[3][3];
myarray
的類型是“ int
的3元素數組的3元素數組”。 按照上述規則,當您編寫時
MyFunction(myarray, 3, 3);
表達 myarray
具有從“3元素數組的3元素數組其類型隱式轉換(“衰變”) int
”到“指針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
將產生不同的值,具體取決於a
是int **
還是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]
的結果是一個整數,而不是一個指針,因此我們必須在一個下標操作中計算數組的完整偏移量。 請注意,此技巧僅適用於真正的多維數組。
現在, **
可以指示以2D結構組織的值,但是以不同的方式構建的值可以指示。 例如:
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);
...
}
根據上面的規則,當表達式去d0
, d1
,和d2
出現在初始值設定為a
,它們的類型全部從“3元素數組轉換int
”到“指針int
”。 類似地,當表達a
出現在調用AnotherFunc
,其類型是從“指針的3元素數組轉換int
”到“指針指向int
”。
請注意,在AnotherFunc
我們對兩個維度都下標,而不是像在MyFunc
那樣計算偏移量。 這是因為a
是指針值的數組。 表達式arr[i]
使我們從位置arr
偏移第i個指針值; 然后,我們找到該指針值的第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
高維數組的模式應該清楚。
編輯:這是我根據您的新示例代碼嘗試根據要求提供更全面的答案:
無論數組的尺寸如何,您傳遞的都是“指向數組的指針”-盡管指針的類型可以變化,但這只是單個指針。
在您的第一個示例中, int array[6]
是6個int
元素的數組。 傳遞array
將指針指向第一個元素,即int
,因此參數類型為int *
,可以等效地寫為int []
。
在第二個示例中, int array[3][3]
是3行(元素)的數組,每行包含3個int
。 傳遞array
會將指針傳遞給第一個元素, 該元素是3個int
s的數組 。 因此,類型為int (*)[3]
-指向3個元素的數組的指針,可以等效地寫為int [][3]
。
希望您現在看到了區別。 當您傳遞int **
,它實際上是指向int *
s數組的指針,而不是指向2D數組的指針。
實際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 *
s的數組,將其作為參數傳遞將導致一個int **
。
原始答案:
您的第一個示例並不是真正的2D數組,盡管它的用法類似。 在那里,您正在創建ROWS
個char *
指針,每個指針指向一個不同的COLS
字符數組。 這里有兩個間接級別。
第二個和第三個示例實際上是2D數組,其中整個ROWS * COLS
字符的內存是連續的。 這里只有一個間接級別。 指向2D數組的指針不是char **
,而是char (*)[COLS]
,因此您可以執行以下操作:
char (*p)[SIZE] = arr;
// use p like arr, eg. p[1][2]
其他人已經總結了很多。 int ** A表示A是指向數組的指針,而不是對二維數組的引用。 但是,這並不意味着它不可用。 由於C中的數據按行優先順序存儲,因此一旦知道行長,就可以輕松地檢索數據
因為指針指針與數組指針的類型不同。 有關詳細信息,請參見指向指針的指針和指針數組 。
此外,這還有一些很好的信息: http : //c-faq.com/aryptr/index.html
第一個例子是可能的,因為當作為函數參數傳遞時,數組會退化為指針。
第二個示例不起作用,因為int[3][3]
退化為int (*)[3]
, 而不是雙指針int **
。 之所以如此,是因為2D數組在內存中是連續的,沒有這些信息,編譯器將不知道如何訪問第一行之后的元素。 考慮一個簡單的數字網格:
1 2 6
0 7 9
如果將這些數字存儲在int nums[6]
數組中,我們將如何索引到數組中以訪問元素7? 當然,乘以1 * 3 + 1
或更普遍地,按row * num-columns + column
。 為了訪問第一行之后的任何元素,您需要知道網格有多少列。
當您將數字存儲為nums[2][3]
,編譯器將使用與對一維數組手動進行操作時完全相同的row * num-columns + column
算術,這對程序員而言是隱藏的。 因此,在傳遞2D數組時必須傳遞列數,以使編譯器能夠執行此算法。
在許多其他語言中,數組攜帶有關其大小的信息,從而無需在將多維數組傳遞給函數時手動指定尺寸。
如果您想獲得更精確的答案,也許我們可以期待一個更“精確”的問題。 您的想法有兩個問題:
int A[3][3]
時,它會衰減到其第一個元素的地址,從而會指向int (*)[3]
類型的指針。 為了能夠通過數組,您必須使用&A[0][0]
獲得指向第一個“內部”成員的指針。 A[i][j]
因為您的編譯器在那里沒有行長的信息。 此代碼有兩個主要問題。
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);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.