为什么不能将数组作为函数参数传递?
我一直在阅读这本C ++书,上面写着“你不能将数组作为函数参数传递”,但它从未解释过为什么。此外,当我在网上查找时,我发现了一些评论,比如'你为什么要这样做?'这不是我会这样做的,我只是想知道为什么你不能这样做。
答案 0 :(得分:45)
为什么数组不能作为函数参数传递?
他们可以:
void foo(const int (&myArray)[5]) {
// `myArray` is the original array of five integers
}
在技术术语中,foo
的参数类型是“引用5 const
int
s的数组”;通过引用,我们可以围绕传递实际对象(免责声明:术语因抽象级别而异)。
您不能做的是按值传递 ,因为由于历史原因,我们不会复制数组。相反,尝试按值将数组传递给函数(或传递数组的副本)会导致其名称衰减为指针。 (some resources get this wrong!)
这意味着:
void foo(int* ptr);
int ar[10]; // an array
foo(ar); // automatically passing ptr to first element of ar (i.e. &ar[0])
还有一个非常误导性的“语法糖”看起来就像你可以按值传递一个任意长度的数组:
void foo(int ptr[]);
int ar[10]; // an array
foo(ar);
但是,实际上,你仍然只是传递一个指针(ar
的第一个元素)。 foo
与上面相同!
虽然我们正在使用它,但以下函数也并没有真正具有它的签名。看看当我们尝试调用此函数而不定义它时会发生什么:
void foo(int ar[5]);
int main() {
int ar[5];
foo(ar);
}
// error: undefined reference to `func(int*)'
所以foo
实际上需要int*
,不 int[5]
!
You can hack around this将数组包装在struct
或class
中,因为默认的复制操作符将复制数组:
struct Array_by_val
{
int my_array[10];
};
void func (Array_by_val x) {}
int main() {
Array_by_val x;
func(x);
}
这有点令人困惑。
在C ++中,通过一些模板魔术,我们可以使一个函数既可重用又能够接收数组:
template <typename T, size_t N>
void foo(const T (&myArray)[N]) {
// `myArray` is the original array of N Ts
}
但我们仍然无法通过价值传递一个。要记住的东西。
由于C ++ 11刚刚开始,并且C ++ 0x支持在主流工具链中得到很好的支持,你可以使用继承自Boost的可爱std::array
!我会把研究作为练习留给读者。
答案 1 :(得分:14)
所以我看到答案解释,“为什么编译器不允许我这样做?”而不是“是什么导致标准指定这种行为?”答案在于C的历史。这取自Dennis Ritchie的“C语言的发展”(source)。
在原型C语言中,记忆被分为“单元格”,每个单元格包含一个单词。这些可以使用最终的一元*
运算符取消引用 - 是的,这些基本上是无类型语言,就像今天的一些玩具语言,如Brainf_ck。语法糖允许人们假装指针是一个数组:
a[5]; // equivalent to *(a + 5)
然后,添加了自动分配:
auto a[10]; // allocate 10 cells, assign pointer to a
// note that we are still typeless
a += 1; // remember that a is a pointer
在某些时候,auto
存储说明符行为变为默认行为 - 您可能也想知道auto
关键字的重点是什么,就是这样。由于这些增量变化,指针和数组的行为有些古怪。如果语言是从鸟瞰图设计的,那么这些类型的行为可能更相似。就目前而言,这只是一个C / C ++问题。
答案 2 :(得分:5)
数组在某种意义上是第二类,C ++继承自C。
在the C99 standard中引用6.3.2.1p3:
除非是 sizeof 运算符或一元运算符的操作数 &安培; operator,或者是用于初始化数组的字符串文字,a 类型为“ type ”的表达式转换为 带有“指向 type 的指针”的表达式,指向初始值 数组对象的元素,而不是左值。如果是数组对象 具有寄存器存储类,行为未定义。
C11 standard中的相同段落基本相同,添加了新的_Alignof
运算符。 (两个链接都是非常接近官方标准的草稿。(更新:这实际上是N1570草案中的一个错误,在已发布的C11标准中得到纠正。_Alignof
不能应用于表达式,仅适用于带括号的类型名称,因此C11只有与C99和C90相同的3个例外。(但我离题了。)))
我没有相应的C ++引用方便,但我相信它非常相似。
因此,如果arr
是数组对象,并且您调用函数func(arr)
,则func
将收到指向arr
的第一个元素的指针。
到目前为止,这或多或少“它的工作原理是因为它的定义方式”,但有历史和技术原因。
允许数组参数不允许很大的灵活性(不需要对语言进行进一步更改),因为例如char[5]
和char[6]
是不同的类型。即使通过引用传递数组也没有用(除非我缺少一些C ++特性,总是有可能)。传递指针会给你极大的灵活性(也许太多了!)。指针可以指向任何大小的数组的第一个元素 - 但是你必须使用自己的机制来告诉函数该数组有多大。
设计一种语言,使不同长度的数组在某种程度上兼容,同时仍然是不同的,实际上非常棘手。例如,在Ada中,char[5]
和char[6]
的等价物是相同的类型,但不同的子类型。更多动态语言使长度成为数组对象值的一部分,而不是其类型。 C仍然非常混乱,显式指针和长度,或指针和终止符。 C ++从C继承了所有这些包袱。它主要是对整个数组的事情进行了抨击并引入了向量,因此没有那么多需要使数组成为一流的类型。
TL; DR:这是C ++,你应该使用矢量! (好吧,有时候。)
答案 3 :(得分:2)
数组不是按值传递的,因为数组本质上是连续的memmory块。如果你有一个想要按值传递的数组,你可以在结构中声明它,然后通过结构访问它。
这本身会影响性能,因为这意味着您将锁定堆栈上的更多空间。传递指针的速度更快,因为要复制到堆栈中的数据包络要少得多。
答案 4 :(得分:1)
我认为C ++之所以这样做,是因为它在创建时可能占用了太多资源来发送整个数组而不是内存中的地址。这只是我对这件事和一个假设的看法。
答案 5 :(得分:1)
这是因为技术原因。参数在堆栈上传递;一个数组可以有一个巨大的,兆字节和更多。在每次调用时将数据复制到堆栈不仅会更慢,而且会很快耗尽堆栈。
您可以通过将数组放入结构(或使用Boost :: Array)来克服该限制:
struct Array
{
int data[512*1024];
int& operator[](int i) { return data[i]; }
};
void foo(Array byValueArray) { .......... }
尝试对该函数进行嵌套调用,看看你会得到多少堆栈溢出!