为什么数组不能作为函数参数传递?

Why can't arrays be passed as function arguments?

本文关键字:函数 参数传递 数组 不能 为什么      更新时间:2023-10-16

为什么不能将数组作为函数参数传递?

我一直在读这本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*)'

所以fooint*事实上,不是 int[5] !

(现场演示)。


但是你可以绕过它!

您可以通过将数组包装为structclass来解决这个问题,因为默认的复制操作符复制数组:
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) { .......... }

尝试对该函数进行嵌套调用,看看你会得到多少堆栈溢出!