为什么数组不能作为函数参数传递?
Why can't arrays be passed as function arguments?
为什么不能将数组作为函数参数传递?
我一直在读这本c++书,书中说"你不能把数组作为函数参数传递",但它从来没有解释为什么。而且,当我在网上查到这样的评论时,我发现"你为什么要这样做?"不是说我会这么做,我只是想知道你为什么不能。
为什么数组不能作为函数参数传递?
他们可以:
void foo(const int (&myArray)[5]) {
// `myArray` is the original array of five integers
}
在专业术语中,foo
的实参类型为"对5个const
int
s数组的引用";使用引用,我们可以在周围传递实际的对象(免责声明:术语因抽象级别而异)。
不能通过值传递,因为由于历史原因,我们不能复制数组。相反,尝试按值将数组传递给函数(或者传递数组的副本)会导致其名称衰减为指针。(有些资源搞错了!)
数组名衰变为值传递指针
这意味着:
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]
!
(现场演示)。
但是你可以绕过它!
您可以通过将数组包装为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
!我将把研究这个问题留给读者作为练习。
所以我看到答案解释,"为什么编译器不允许我这样做?"而不是"是什么导致标准指定了这种行为?"答案就在C语言的历史中。这段话摘自Dennis Ritchie的《C语言的发展》。
在原始的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++的又一个问题。
数组在某种意义上是二级类型,是c++从C继承来的。
引用C99标准6.3.2.1p3:
为sizeof操作符或一元数的操作数时除外,操作符,或用于初始化数组的字符串字面值类型为"array of 类型"的表达式将被转换为类型为"指向类型的指针"的表达式元素,且不是左值。如果数组对象具有注册存储类,行为未定义。
C11标准中的同一段基本上是相同的,只是增加了新的_Alignof
操作符。(这两个链接都指向与官方标准非常接近的草案。(UPDATE:这实际上是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++,你应该使用向量!(嗯,有时候。)
数组不是按值传递的,因为数组本质上是连续的内存块。如果你有一个数组要按值传递,你可以在结构体中声明它,然后通过结构体访问它。
这本身就对性能有影响,因为这意味着你将在堆栈上锁定更多的空间。传递指针更快,因为要复制到堆栈上的数据包要少得多。
我认为c++这样做的原因是,当它被创建时,它可能占用了太多的资源来发送整个数组,而不是内存中的地址。这只是我对这件事的想法和假设。
这是由于技术原因。参数在堆栈上传递;数组可以有很大的大小,兆字节甚至更多。在每次调用时将这些数据复制到堆栈中不仅会变慢,而且会很快耗尽堆栈。
可以通过将数组放入struct(或使用Boost:: array)来克服这个限制:
struct Array
{
int data[512*1024];
int& operator[](int i) { return data[i]; }
};
void foo(Array byValueArray) { .......... }
尝试对该函数进行嵌套调用,看看你会得到多少堆栈溢出!
- 如何在C++中将迭代器作为函数参数传递
- 为什么我不能将引用作为 std::async 的函数参数传递
- 节点插件 API 将数组作为函数参数传递
- 将字符串数组作为函数参数传递
- 如何通过 C++ 中的函数参数传递初始值设定项列表的固定大小的初始值设定项列表?
- 我可以动态创建新地图并作为函数参数传递吗?
- 将任意模板向量作为函数参数C++传递
- 将函数指针作为函数参数传递的目的
- C++:通过函数参数传递的值给出不同的结果
- 当数组大小在C++中未知时,如何在运行时将对字符串数组的引用作为函数参数传递?
- 通过宏将函数参数传递给函数
- C++:访问作为函数参数传递的双指针数组所指向的变量的值
- 如何将 std::string 作为构造函数参数传递,并将其保存的 C 字符串存储在 void 指针中?
- 将派生类构造函数参数传递给受保护的成员
- 将指向数组的指针作为函数参数传递,这本身就是另一个函数的返回值?
- 如何将二维数组类型字符(字符串)作为函数参数传递?
- 有什么理由C++ 11+ std::mutex 应该声明为全局变量,而不是作为函数参数传递到 std::thread 中
- 如何将派生类作为函数参数传递:如果派生类成员不在父类中怎么办
- 如何将变量作为构造函数参数或函数参数传递
- 当数组对象以函数参数传递时,为什么复制构造函数会自称