关于指针和多维数组的困惑

如果可能的话:

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”的表达式转换为类型为“指向类型的指针”的表达式指向数组对象的初始元素,而不是左值。 如果数组对象具有寄存器存储类,则行为未定义。

鉴于声明

 int myarray[3][3]; 

myarray类型是“ int元素的3元素数组的3元素数组”。 按照上面的规则,当你写作时

 MyFunction(myarray, 3, 3); 

表达式 myarray的类型隐式转换(“衰减”)从“3元素数组int的3元素数组”转换为“指向3元素数组的int ”或int (*)[3]

因此,您的函数原型需要

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

注意int **arrayint (*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); ... } 

按照上面的规则,当表达式d0d1d2出现在a的初始化器中时,它们的类型都从“3元素数组int ”转换为“指向int ”。 类似地,当表达式a出现在对AnotherFunc的调用中AnotherFunc ,其类型将从“指向int 3元素数组”转换为“指向int指针”。

请注意,在AnotherFunc我们下标两个维度而不是像在MyFunc那样计算偏移量。 那是因为a是一个指针值数组。 表达式arr[i]使我们得到第i个指针值偏离位置arr ; 然后我们找到该指针值的第j个整数值偏移量。

下表可能有所帮助 - 它显示了各种数组表达式的类型以及它们根据其声明衰减的内容( T (*)[N]是指针类型,而不是数组类型,因此它不会衰减):

声明表达式类型隐式转换(衰减)为
 ----------- ---------- ---- ------------------------- -------
      T a [N] a T [N] T *
                                &a T(*)[N]
                                *在
                              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个int (元素)的3行(元素)数组。 传递array传递一个指向第一个元素的指针,该元素是一个3个int的数组 。 因此类型是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是指向数组的指针,而不是对2-D数组的引用。 但是,这并不意味着它不可用。 由于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] ,编译器使用与使用1D数组手动完全相同的row * num-columns + column算法,它只是对程序员隐藏。 因此,在传递2D数组时必须传递列数,以便编译器能够执行此算法。

在许多其他语言中,数组携带有关其大小的信息,从而无需在将多维数组传递给函数时手动指定维度。

也许我们可以期待一个更“重要”的问题,如果你想得到更多的答案。 你的想法有两个问题:

  1. 一个二维数组int A[3][3]当在一个表达式中使用时衰减到它的第一个元素的地址,从而衰减为int (*)[3]类型的指针。 为了能够通过数组,您必须使用&A[0][0]来获取指向第一个“内部”成员的指针。
  2. 在函数内部,由于编译器没有行长度的信息,因此无法执行操作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]这会求值为类似于B[1]int * ,除了你可以说A[1] = (int *)0x4444; ,但如果你说B[1] = (int *)0x4444; 你会得到编译器错误,因为B[1]实际上是一个计算值,而不是一个变量。 对于B ,没有int *变量数组 – 只是根据行大小和数组第一个成员的地址进行的一些计算。

此代码应该执行类似于您想要的操作(为了可读性,某些输出格式更改)。 请注意print语句中的索引值是如何更改的。

 MyFunction(int *array, int row, int col) { int x = 0; for(int i=0 ; i