如何将多维 C 数组传递给函数?

3
我正在学习C语言和指针,在大学课程中,我认为我对这个概念有了相当好的掌握,但是我对多维数组和指针之间的相似性感到困惑。
我以为所有的数组(甚至是多维数组)都被存储在连续的内存中,所以你可以安全地将它转换为一个int*(假设给定的数组是int[])。然而,我的教授说定义中星号的数量取决于数组的维数。因此,int[]将变成int*,int[][]将变成int**等。
于是我写了一个小程序来测试这个问题:
void foo(int** ptr)
{    
}

void bar(int* ptr)
{    
}

int main()
{
    int arr[3][4];

    foo(arr);
    bar(arr);
}

出乎意料的是,编译器在 两个 函数调用上都发出了警告。
main.c:22:9: warning: incompatible pointer types passing 'int [3][4]' to parameter of type
      'int **' [-Wincompatible-pointer-types]
    foo(arr);
        ^~~
main.c:8:16: note: passing argument to parameter 'ptr' here
void foo(int** ptr)
         ^
main.c:23:9: warning: incompatible pointer types passing 'int [3][4]' to parameter of type
      'int *' [-Wincompatible-pointer-types]
    bar(arr);
        ^~~
main.c:13:15: note: passing argument to parameter 'ptr' here
void bar(int* ptr)
         ^
2 warnings generated.

这是怎么回事?你是如何更改函数调用,使其中一个接受数组的?
我的原问题被标记为重复,由于我无法弄清楚如何删除重复项,所以我再次提问。
编辑:这与如何将多维数组传递给C和C++中的函数不同,因为我正在询问如何更改函数调用本身,而不是函数签名。我知道其中一个函数是正确的,但我不确定哪一个或者如何调用它。
编辑2:这是我的原始问题的重复,但原始问题被错误地标记为重复,并且无法得到答案,因此我重新提出了这个问题。

3
您需要编辑旧问题,将其清楚地说明不是重复问题(包括链接您参考的帖子),然后等待获得足够的重新开放投票来删除重复关闭。您不应该重新发布重复的问题。[Help]页面提供了更多关于如果您的问题被关闭应该做什么的信息;它在“提问”部分中。 - Ken White
这部分是您链接的问题的部分重复。数组的数组与指向指针的指针不同。请参见例如我之前的这个答案以了解原因。 - Some programmer dude
下次你的问题被关闭并想要重新开启时,请不要接受任何答案。如果你接受了答案,就意味着你对它感到满意,因此没有理由重新开启它。 - Fabio says Reinstate Monica
3个回答

7
然而,我的教授说定义中星号的数量取决于数组中维度的数量。因此,一个int[]将变成int*int[][]将变成int**等。
我真的,非常地希望你误解了他的意思,因为那是不正确的。
除非它是sizeof或一元&运算符的操作数,或者是用于声明中初始化字符数组的字符串文字,否则类型为“T的N个元素的数组”的表达式将被转换(“衰减”)为类型为“指向T的指针”的表达式,并且表达式的值将是数组的第一个元素的地址。如果T是一个数组类型,你会得到一个指向数组的指针,而不是指向指针的指针。
通过一些例子来说明:
int arr[10];
...
foo( arr, 10 ); // need to pass number of rows as a separate parameter

在调用 foo 函数时,表达式 arr 的类型为 "10个元素的int数组"。由于它不是 sizeof 或一元 & 运算符的操作数,所以它会"衰减"为类型 int *,并且表达式的值将是第一个元素的地址。因此,foo 函数的原型应该是:
void foo( int *arr, size_t rows ); // or int arr[]; it means the same thing in this context

注意,这与表达式&arr[0]的结果相同。 arr[0]是一个int对象,因此&arr[0]给出了一个int *。 换句话说,arr == &arr[0]
到目前为止一切都很好。现在让我们看一个二维数组:
int arr[10][20];
...
foo( arr, 10 );

在这种情况下,表达式arr的类型为“10个元素的数组,每个元素是20个元素的int数组”,它“衰减”成为一个类型为“指向20个元素的int数组”的表达式;因此,foo的原型变为:
void foo( int (*arr)[20], size_t rows ); // or int arr[][20]

记得前面有一部分说到“除非它是一元 & 运算符的操作数”,如果我们写成 &arr[0],那么arr[0] 的类型为“20个 int 元素的数组”,但是它不会自动退化为指针。因此,我们得到的表达式类型是int (*)[20]而不是int **。因此,arr == &arr[0]
现在让我们看一个三维数组:
int arr[10][20][30];
...
foo( arr, 10 );

这次,表达式 arr 的类型为“包含10个 20元素数组的30元素数组的数组”。这一次,它“衰变”成了类型为“指向 20元素数组的30元素数组的指针”的表达式,并且原型现在变成了:
void foo( int (*arr)[20][30], size_t rows ); // or int arr[][20][30]

再一次地,arr[0] 是一个数组类型,因此表达式 &arr[0] 给出了类型 int (*)[20][30];同样,arr == &arr[0]

更高维数组的模式从这里开始变得清晰。

现在,这带来了一个小问题。指向 N 元素数组的指针是一种不同于指向 M 元素数组的指针的类型,其中 N != M。如果你的函数原型是

void foo( int (*)[20], size_t rows );

那么它只能使用具有Nx20数组的数组指针;您不能将其传递给具有不同外部维度的数组指针:

void foo( int (*ap)[20], size_t rows );
...
int arr1[10][20];
int arr2[20][20];
int arr3[20][30];

foo( arr1, 10 ); // okay
foo( arr2, 20 ); // okay
foo( arr3, 20 ); // not okay - arr3 has type int (*)[30], which is not *compatible* 
                 // with int (*)[20]

编辑

如果一个函数需要一个 int * 类型的参数,而你有一个多维数组,你需要显式地传递一个指向第一个元素的指针:

void foo( int *, size_t size );
...
int arr2[10][20];
int arr3[20][10][5];

foo( &arr2[0][0], sizeof arr2 / sizeof arr2[0][0] );
foo( &arr3[0][0][0], sizeof arr3 / sizeof arr3[0][0][0] );

很不幸,我特别记得他说过指针定义中维度的数量就是星号的数量。不过还是非常感谢你的详细解释,现在我明白多了,点赞+1。 - Dovahkiin

2

仔细检查数据类型,你就能搞定。

对于函数 void bar(int* ptr) {... 它期望一个 int *,你必须传递一个。类似这样的:

 bar (&(arr[0][0]));

会做。

话虽如此,一般来说,数组并不等同于指针。在大多数情况下,数组会衰变为指向数组第一个元素的指针,但是数据类型应该相同(或至少是兼容的)。


哦哦哦...这就有意义了。所以期望一个 int** 的那个是不正确的? - Dovahkiin
@Dovahkiin 再次确认一下,你是要传递一个 int ** 吗?还是 int (*)[4] - Sourav Ghosh
我猜不是...所以我的教授错了? - Dovahkiin
1
@Dovahkiin 详细说明一下,指向指针的int和指向包含4个int的数组的指针...应该有一些区别,不是吗? - Sourav Ghosh
@Dovahkiin 也可以看看这个 - Sourav Ghosh

0
假设,a = [1,2,3]
在这里,a是指向数组第一个元素的指针。
*a = 1 , *(a+1) = 2, *(a+2) = 3.

现在,如果a是一个多维数组,比如a[3][4]

*a = a[0][0] , *(a+1) = a[1][0], *(a+2) = a[2][0]

*(*a + 1) = a[0][1], *(*a + 2) = a[0][2]

*(*(a+1) + 1) = a[1][1], *(*(a+1) + 2) = a[1][2]

*(*(a+2) + 1) = a[1][1], *(*(a+2) + 2) = a[1][2]

*a + 任意数字可以改变行,但是*(*a + 任意数字)可以改变列。

在函数中,你只需要传递数组的指针,也就是a。

因此,

void bar(*a){
#access elements like told above.
}

双指针 **b,它意味着它指向另一个指针,也就是说,如果
*b = a
代码相关内容:

b的值是a的地址,即*b和a具有相同的值。 但是**b指向a指向的位置。

这意味着**b和*a具有相同的值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接