为什么C/C++允许在函数调用中省略多维数组的最左边索引?

16

我想知道为什么在将多维数组传递到函数时可以省略最左边的索引?为什么不是省略多个索引?如果省略一个索引,编译器如何确定大小?


你能提供一些代码作为例子吗? - Drahakar
3个回答

16

其他答案介绍了C标准如何处理数组到指针的转换以及这如何影响函数声明,但我觉得他们没有深入探讨 为什么,所以在这里我来说明一下...

在C中,数组表示内存中紧密打包的元素。

A -> _ _ _ _ _ _ ...
i:   0 1 2 3 4 5 ...
在上面的例子中,每个数组元素都是1 _宽度。要找到第i个元素,我们需要转到第i个地址。(请注意,这里最左边的维度(大小)并不重要)
现在考虑一个多维数组:
B -> [_ _ _][_ _ _][_ _ _][_ _ _]...
i:    0 0 0  1 1 1  2 2 2  3 3 3
j:    0 1 2  0 1 2  0 1 2  0 1 2
     ^first row    ^third row
为了找到A[i][j]的偏移量,我们需要跳过i行(3*i)和j个元素->(3*i+j)。请注意,这里也不需要第一维的大小。
现在应该清楚了,当使用数组时,最左侧的大小是不必要的,它只在创建数组时需要。
既然没有必要给出最左侧索引的维度,为什么不为完整性而指定呢?毕竟,这就是Pascal编程语言(C的同时代)所做的事情。
好吧,大多数操作数组的函数对所有可能的数组长度都起作用,因此指定大小只会损害您重用它们的能力。
例如,为什么要
int sum(int arr[10]){
    int s = 0, i;
    for(i=0; i<10; i++){
        s += arr[i];
    }
    return s;
}

当您可以使用以下方法时:

int sum(int arr[], int n){
    int s = 0, i;
    for(i=0; i<n; i++){
        s += arr[i];
    }
    return s;
}

对于省略多个维度而言,当使用正常的多维数组时是不可能实现的(因为您需要知道每一维度的大小来确定第一行在哪里结束以及第二行在哪里开始)。然而,如果您愿意为临时空间花费一些额外的空间,那么完全可以使用指向指针的指针实现: http://www.eskimo.com/~scs/cclass/int/sx9b.html


你的绘图帮了大忙,非常感谢。 - Marco M.

7

在声明中

实际上,你不能完全省略最右边或最左边的维度。

但是,如果你有一个初始化器,那么最左边的维度可以为你推导出来。

在函数参数列表中

当你通过值将数组传递到函数中时,实际上你传递的是指向该数组第一个元素的指针。是的,从语法上看,它看起来像是你在传递一个数组,但实际上不是这样。

考虑以下例子:

void f(int ar[3])

void f(int ar[])

这两种语法是等价的,但容易让人感到困惑:

void f(int* ar)

没有任何迹象表明有一个数组,更不用说一个特定含有三个元素的数组了。
现在:
void f(int ar[][3])

这是等效语法的混乱语法:
void f(int (*ar)[3])

int (*)[3]是指向您的数组第一个元素(指向 int[3])的指针类型。

总之,不要过于关注类似[]的数组语法;它并不能真正代表发生了什么。


“array”的声明作为多维数组必须为所有维度(除第一维)指定边界。哪里有证据表明第一维不能这样做?... http://www.ideone.com/kR76X ...啊,我模糊地记得如果它只有一个维度并且您将其留空,则会像指针一样处理它。 - mpen
@Philip:Pfft,关于他传递给函数的那一部分是对问题的编辑。我会在我的答案中添加。 - Lightness Races in Orbit
1
@Mark:你可能在想当一个函数有一个参数 int ar[] 时;然而,这与声明无关。 :) - Lightness Races in Orbit
@BarryTheHatchet:你在答案中提供的链接已经失效了。 - Destructor
@析构函数该死 - Lightness Races in Orbit
显示剩余4条评论

3
除非它是sizeof或一元&运算符的操作数,或者是在声明中用于初始化数组的字符串字面值,否则类型为"N个元素的T数组"的表达式将被隐式转换为"指向T的指针"类型,并将计算为数组中第一个元素的地址。
所有这些与你的问题有什么关系呢?
假设以下代码行:
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
foo(arr);

我们将数组表达式arr作为参数传递给foo。由于arr不是sizeof&的操作数,因此它的类型会隐式转换为“指向int的指针”,而不是一个数组。因此,我们传递给foo的是一个指针值,而不是一个数组。

实际上,在函数参数声明中,T a[]T a[N]T *a的同义词;所有三个声明都将a声明为指向T的指针,而不是T的数组。

我们可以将foo的原型定义写成:

void foo(int *a)     // <- foo receives a pointer to int, not an array

或者

void foo(int a[])    // <-- a[] is a synonym for *a

这两个表达式的意思相同,都将a声明为指向整数的指针。

现在让我们看一下多维数组。假设有以下代码:

int arr[10][20];
foo(arr);

表达式arr的类型为“包含10个20元素的数组,其中每个元素都是一个int数组”。根据上述规则,它将隐式转换为“指向20个int元素的数组的指针”。因此,foo的原型定义可以写成:
void foo(int (*a)[20])  // <-- foo receives a pointer to an array, not an array of arrays

或者

void foo(int a[][20])  // <-- a[][20] is a synonym for (*a)[20]

同样地,两者都将a声明为一个指针,而不是一个数组。

这就是为什么在函数参数声明中可以省略最左边的(唯一的)数组索引。


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