我正在尝试运行此代码,但有一个编译器警告:
对于调用打印函数的语句
打印(3,4);
从不兼容的指针类型传递"print"的参数 1 [-Wincompatible-pointer-types] main.c/arrays line 23 C/C++ 问题
对于 printf 语句:
格式 '%d' 需要类型为 'int' 的参数,但参数 2 的类型为 'int *' [-Wformat=] main.c/arrays line 6 C/C++ 问题
代码是将 2D 数组传递给一个函数,该函数将其作为双指针接收并在函数内打印。
void print (int **A, int m, int n){
for(m = 0; m < 3; m++){
for(n = 0; n < 4; n++){
printf("%d ", *((A+(m * 4) + n)));
}
printf("n");
}
}
int main()
{
int arr[][4]={{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
};
print(arr,3,4);
return 0;
}
1) 在 C 中将 2D 数组作为双指针传递是不正确的? 这里 上面的链接仅指向C++?或者这在 C 中也是可能的?
2)如果我使用单个指针,那么以下哪些作业是正确的/不正确的?
清单 1:
int *ptr;
ptr = &arr[0][0];
for(int i = 0; i < 3; i++){
for(int j = 0; j < 4; j++){
printf("%d ", *(ptr + (i * 4) +j));
}
printf("n");
}
清单 2:
int * PTR; PTR = 到达;
让我们从:
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-type"的表达式将转换为类型为">指针类型"的表达式,该表达式指向数组对象的初始元素,而不是左值。
以下是此规则如何应用于print(arr,3,4);
中的arr
:
arr
是一个标识符,这意味着它是某个对象的名称。因此,它指定其对象。该对象是一个由 3 个数组组成的数组,每个数组的数组为 4 个int
。- 这个数组不是
sizeof
或Alignof
或&
的操作数,也不是字符串文字。因此,按照规则,它从 3 个 4int
个数组的数组转换为指向第一个 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)
受加法更紧密的约束,那么为什么它们的间距更宽松呢?A + m*4
会更好地描绘含义。然后A+(m * 4) + n
意味着添加n
。总的来说,我们已经将m*4+n
元素移到了A
点之外。由于A
指向指针,因此我们将指针前进了m*4+n
指针。然后*((A+(m * 4) + n)))
取消引用它。当您取消引用指向int
的指针时,您将获得指向int
的指针。所以这个表达式的结果是一个指针。但是你想要一个int
.
您引用的链接谈到了"2D 阵列"。它所讨论的数组类型是使用指向指针的指针实现的。若要创建此类数组,请创建一个指针数组,然后将每个指针设置为指向行的元素。然后,指向该指针数组的指针就像一个 2D 数组,因为该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)))
是什么?在这种情况下,您将 3 个 4 个int
数组的数组视为一个 12 个int
的大数组。然后计算第m
行的元素n
的位置。在这种情况下,A
是指向int
的指针(而不是指向指向int
的指针)。因此,A+(m * 4) + n
是计算行m
的元素n
的位置,*((A+(m * 4) + n)))
获取该元素的值。
这是您应该尽可能避免的方法。通常,您应该使用 C 的内置方法来寻址数组元素,并避免自己进行计算。它是否严格符合 C 代码可能取决于您对解释 C 标准中某些段落的迂腐程度。
你期望二维数组会衰减到指向指针的指针是没有根据的。
为了能够使用arr
作为参数来print
,您有以下选项。
-
将
print
更改为void print (int (*A)[4], int m){ // Not need for n. It is 4
-
将
print
更改为使用 VLA。为此,m
和n
必须在A
之前。void print(int m, int n, int A[m][n] {
这两项更改都要求您同时更改呼叫。
如果你的意图是一个指针数组,你不能一次初始化它。您的数组编译为与{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
相同的内容
下面编译为指向数组的指针数组:
int arr1[] = {1, 2, 3, 4};
int arr2[] = {5, 6, 7, 8};
int arr3[] = {9, 10, 11, 12};
int* my2DArr[3] = {arr1, arr2, arr3};
它对嵌套循环有效,如下所示:
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 4; j++)
{
printf("%dn", my2DArr[i][j]);
}
}
对于代码中的数组,可以像循环访问一个数组一样循环访问它。