在 c 警告消息中将数组作为双指针传递



我正在尝试运行此代码,但有一个编译器警告:

对于调用打印函数的语句

打印(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
  • 这个数组不是sizeofAlignof&的操作数,也不是字符串文字。因此,按照规则,它从 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。(顺便说一下,这是奇怪的间距。m4受更高优先级乘法的约束比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将指针的值进一步向前推进到点nint。那是我们应该找到第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将指针的值进一步向前推进nint点。这就是我们应该找到第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,您有以下选项。

  1. print更改为

    void print (int (*A)[4], int m){ // Not need for n. It is 4
    
  2. print更改为使用 VLA。为此,mn必须在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]);
}
}

对于代码中的数组,可以像循环访问一个数组一样循环访问它。

最新更新