先看代码:
#include <cstdio>
void foo(int array[2])
{
printf("int array[2]:\t\t%x %d\n", &array, sizeof(array));
}
void bar(int array[])
{
printf("int array[]:\t\t%x %d\n", &array, sizeof(array));
}
void baz(int (&array)[2])
{
printf("int (&array)[2]:\t%x %d\n", &array, sizeof(array));
}
int main()
{
int a[2] = {1, 2};
printf("main::a[2]:\t\t%x %d\n", a, sizeof(a));
foo(a);
bar(a);
baz(a);
return 0;
}
输出:
main::a[2]: 28feb8 8
int array[2]: 28fea0 4
int array[]: 28fea0 4
int (&array)[2]: 28feb8 8
解说:
foo和bar的传值方式是相同的,都是一个int*, 即一个整型指针,这可以从foo和bar里打印出的array地址和main中的不同和sizeof(array)仅为sizeof(int*)看出,只不过是外型有点儿区别。
编译器是不知道你要传递的是一个数组或是单一一个整型的地址的,这是因为C中数组的内存模型是连续存储(它并不知道传递的(首)地址之后的空间可否访问)。
所以写为foo或bar的样式仅仅是对人的一种暗示,暗示传递的是一个数组,括号里的2编译器是不会把他当回事儿的①。
采用foo中的样式,代码编写者在函数中获知传递的数组的大小,但这种暗示功能很弱,而且易使人产生误解。
比如以上的函数foo,传递大小为1个元素的数组(即单一一个整型的地址):
int x[1];
foo(x);
或传递一个大小为100的数组:
int x[100];
foo(x);
编译器都不会有任何抱怨,所以在代码工程量很大的时候,你无法保证数组传值的安全性,另外一个问题是如果你写的是商业性质的库,你无法保证客户(二次开发者)能安全地使用你的代码。
采用bar中样式,实质和foo相同,空括号给人的暗示就是它能接受的参数是一个数组,而且是一个长度不确定的一维整型数组,这相对于foo来说更为实际和真实一些(因为foo可能造成欺骗性的代码,原因见上)。
所以这种传数组的方式被多数人所采用,但一般还需多加一个参数来指定数组的大小,如:
void bar(int array[], int size);
或效仿STL的做法,传递数组的首地址和超尾指针(在遍历数组元素时很方便,且更快速、安全):
void bar(int* beg, int* end);
至于baz,它不同于foo和bar。前面已经说过,foo和bar实质是相同的,传的都是一个int*,且传值方式都是按值传递(C中只有按值传递)。
而baz却是按引用传递,传递的是一个”编译器认可的,大小为2”②的数组的引用。
foo和bar都可以改写为:
void theFact(int* array);
或
void theFact(int* array, int size);
按照此逻辑是不是baz可改写为这样呢?
void baz2(int* const& array); // a其实是一个int* const型指针,所以要加上const作为修饰
答案是否定的,注意上面的②,只有在C++中,函数”按引用”传递数组并”指定其大小时”,[]中的数字才有意义(对编译器而言)。所以baz2 != baz:
int x[100];
baz(x); // 编译错误
baz2(x); // 可以通过
要理解这和foo, bar的不同首先要理解C++中对引用的定义: 引用就是对象本身,不存在没有引用对象的引用。所以在baz中,形参array就是实参main中的a,一切a所有的特性都是array的特性,所以sizeof(array) == sizeof(a),而且&baz::array == main::a(地址相同)。
①: C99中允许使用static数组参数修饰词,如:
void foo(int x[static 10]); // x数组至少含有10个连续元素
上句中的10此时并不是可有可无的,它是编译器优化数组访问的一种暗示。