让我们开始吧:
int arr[][4]={{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
};
print(arr,3,4);
在print(arr,3,4); 中,arr 是一个数组。具体来说,它是一个3个元素的数组,每个元素是一个4个元素的数组,每个元素都是一个int。因此,arr 是一个由 3 个数组组成的数组,每组 4 个int。您可能听说过或读过数组“衰减”为指针。这是一个口语术语。您可以在 C 2011 标准的第 6.3.2.1 条第 3 段中找到实际规则:
除非它是sizeof 运算符、_Alignof 运算符或一元& 运算符的操作数,或者是用于初始化数组的字符串文字,否则表达式类型为“array of type”被转换为类型为“pointer to type”的表达式,它指向数组对象的初始元素,而不是左值。
以下是此规则如何应用于print(arr,3,4); 中的arr:
-
arr 是一个标识符,表示它是某个对象的名称。因此,它指定了它的对象。该对象是一个由 3 个数组组成的数组,每组 4 个 int。
- 这个数组不是
sizeof或Alignof或&的操作数,也不是字符串字面量。因此,按照规则,它从 3 个数组的 4 个int 的数组转换为指向第一个 4 个数组的指针 int。
接下来会发生什么?没有。我们的表达式是一个指向 4 个int 的数组的指针。没有规定将指向数组的指针转换为指向指针的指针。我们有一个指向数组的指针,但该数组还没有在表达式中使用,也没有在简单的表达式arr 中使用。所以它没有被转换。
这意味着您传递给print 的是一个指向4 个int 的数组的指针。但是您对print 的声明说它需要一个指向int 的指针。这些是不同的东西,它们是不兼容的,所以编译器会警告你。
(要查看它们不兼容,请考虑指向 4 int 数组的指针和指向 int 指针的指针的区别。指向 4 int 数组的指针的内存包含 4 int 值。指向指向int 的指针的内存中包含一个指针。这些是非常不同的东西。)
接下来,考虑:
void print (int **A, int m, int n)
…
printf("%d ", *((A+(m * 4) + n)));
我们从上面知道你应该将int **A 更改为int (*A)[4],这是一个指向4 个int 的数组的指针。也可以改成int A[][4],因为C中有一条规则,这样的参数声明会自动调整为int (*A)[4],方便起见。但是,假设您将其保留为int **A。那么*((A+(m * 4) + n))是什么意思?
既然A是一个指向int的指针,那么A+(m * 4)的意思就是在指针上加上m * 4。 (顺便说一下,这是一个奇怪的间距。m 和 4 比 A 和 (m * 4) 更紧密地被高优先级乘法约束,那么为什么它们的间距更松呢?@987654366 @ 会更好地描述含义。)然后A+(m * 4) + n 表示添加n。总的来说,我们已经将m*4+n 元素移到了A 指向的位置之外。由于A 指向一个指针,我们将指针推进了m*4+n 指针。然后*((A+(m * 4) + n))) 取消引用。当您取消引用指向int 的指针时,您将获得指向int 的指针。所以这个表达式的结果是一个指针。但你想要一个int。
您引用的链接谈到“二维数组”。它所讨论的数组类型是使用指向指针的指针来实现的。要创建这样一个数组,您需要创建一个指针数组,然后将这些指针中的每一个设置为指向一行的元素。然后指向该指针数组的指针就像一个二维数组,其中A[i][j] 引用行i 的元素j。如果您有这样的数组,您可以使用A[m][n] 引用行m 的元素n。等效地,您可以使用*(*(A+m)+n) 引用它。这个表达式的意思是:
- 获取指针
A 并将m 添加到它。由于A 指向指向int 的指针,因此添加m 会使指针的值更进一步地指向m 指针。这就是我们应该找到指向行 m 的元素的指针。
-
*(A+m) 获取A+m 指向的指针的值。该值应该是指向行 m 的元素的指针,特别是指向第一个元素(索引为 0)的指针。
-
*(A+m)+n 将指针的值前进到指向 n int 更远。这就是我们应该找到行 m 的元素 n 的地方。
-
*(*(A+m)+n) 获取*(A+m)+n 指向的int 的值。
现在假设您将 print 更改为 print(int A[][4], int m, int n)。那么你的printf 语句应该像以前一样使用A[m][n]。或者它也可以像以前一样使用*(*(A+m)+n)。但是,在这种情况下,表达式会被计算:
-
A 是指向 4 个 int 的数组的指针。将m 添加到它会使指向m 数组的指针的值进一步前进。
-
*(A+m) 获取A+m 指向的对象。这个对象是一个完整的数组。所以这是一个指定数组的表达式。遵循关于表达式中数组的 C 规则,该数组被转换为指向其第一个元素的指针。因此,*(A+m) 成为指向编号为 m 的数组的第一个元素的指针。
-
*(A+m)+n 将指针的值前进到指向 n int 的位置。这就是我们应该找到行 m 的元素 n 的地方。
-
*(*(A+m)+n) 获取*(A+m)+n 指向的int 的值。
因此,A[m][n] 对于指针指向、指向数组的指针和数组中的数组具有相同的最终结果,但是每个步骤所经历的步骤不同。 C 知道每个子表达式的类型并对其进行不同的处理,以达到相同的结果。
最后,假设您将&A[0][0] 传递给print 并将其参数更改为int *A。现在*((A+(m * 4) + n))) 是什么表达式?在这种情况下,您将 4 个 int 的 3 个数组的数组视为 12 个 int 的一个大数组。然后计算行m 的元素n 的位置。在这种情况下,A 是指向 int 的指针(不是指向 int 的指针的指针)。所以A+(m * 4) + n 是计算m 行的元素n 应该在哪里,*((A+(m * 4) + n))) 得到那个元素的值。
这是您应该尽可能避免的方法。通常,您应该使用 C 的内置方法来寻址数组元素,并避免自己进行计算。是否严格符合 C 代码可能取决于您对 C 标准中某些段落的解释程度。