关于指针和多维数组的困惑
如果可能的话:
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 **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]
的结果是整数,而不是指针,所以我们必须在一个下标操作中计算数组的完整偏移量。 请注意,此技巧仅适用于真正的多维数组。
现在, **
可以指示以二维结构组织的值,但是以不同方式构建的值。 例如:
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
的调用中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数组时必须传递列数,以便编译器能够执行此算法。
在许多其他语言中,数组携带有关其大小的信息,从而无需在将多维数组传递给函数时手动指定维度。
也许我们可以期待一个更“重要”的问题,如果你想得到更多的答案。 你的想法有两个问题:
- 一个二维数组
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]
这会求值为类似于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