为啥我们不能通过值传递数组来函数?

Posted

技术标签:

【中文标题】为啥我们不能通过值传递数组来函数?【英文标题】:Why can't we pass arrays to function by value?为什么我们不能通过值传递数组来函数? 【发布时间】:2011-11-19 06:40:57 【问题描述】:

显然,我们可以将复杂的类实例传递给函数,但是为什么我们不能将数组传递给函数呢?

【问题讨论】:

FWIW,这起源于 C 和数组(原始数组,而不是 std::vectorstd::array)几乎没有改变到 C++ 中,我怀疑原因是一样的。跨度> @delnan,原因是一样的吗?什么是“相同”的原因?请更具体。 我相信你可以用 boost::array (或 tr1::array)来做到这一点。 ...(或 std::vector)等。您的问题是关于 40 年前某人为 C 做出的设计决定。您的问题(对于 C++)的答案是“谁在乎”。这对于现代 C++ 来说不是问题,因为尽可能避免声明原始数组(和原始指针)通常是个好主意。最好使用更高级别的数组类,例如我列出的其中一个。 因为 (1) Dennis Ritchie 在 40 多年前在 C 语言中构建了一个指针/数组等价,并且 (2) 这将是一个非常糟糕的主意。 【参考方案1】:

起源是历史的。问题是“数组在传递给函数时衰减为指针”的规则很简单。

复制数组会有点复杂而且不是很清楚,因为行为会因不同的参数和不同的函数声明而改变。

请注意,您仍然可以通过值进行间接传递:

struct A  int arr[2]; ;
void func(struct A);

【讨论】:

在 C++ 中,您可以将数组传递给每个引用的函数。使用函数模板和非类型模板参数,您甚至可以传递任意长的数组。 数组表达式衰减为指针的规则并不特定于函数调用。在 C 中,衰减发生在除 (a) 一元 & 地址操作符的操作数之外的任何上下文中; (b) 一元 sizeof 运算符的操作数;或 (c) 用于初始化字符数组的初始化程序中的字符串文字 (char s[] = "hello";);我认为 C++ 中还有一两个例外,可能涉及引用。例如,在int arr[10]; int *p; p = arr; 中发生了衰减,但看不到函数调用。 这在某种程度上根本无法解释事物的当前状态。使数组可复制并摆脱奇怪的隐式衰减到指针没有问题。然而,这可能需要引入特殊语法来将数组转换为指针(如@arr),表达意图获取指向所有项的指针而不是指向第一项的指针(&(arr[0])),但所有运算符符号已在使用中.或者只是他们当时的代码库不需要数组复制,所以他们决定偷工减料,从长远来看这是一个糟糕的决定。【参考方案2】:

这是另一种观点:C 中没有单一类型的“数组”。相反,T[N] 是每个N不同 类型。所以T[1]T[2]等,都是不同的类型

在 C 中没有函数重载,因此您可以允许的唯一明智的做法是接受(或返回)单一类型数组的函数

void foo(int a[3]);  // hypothetical

据推测,这被认为远不如让所有数组衰减为指向第一个元素的指针并要求用户通过其他方式传达大小的实际决定有用。毕竟上面可以改写成:

void foo(int * a)

  static const unsigned int N = 3;
  /* ... */

因此,表现力没有损失,但总体上获得了巨大的收益。

请注意,这在 C++ 中没有任何不同,但是模板驱动的代码生成允许您编写模板函数 foo(T (&a)[N]),其中为您推导出 N —— 但这只是意味着您可以创建一个整个系列的不同、不同函数,N 的每个值对应一个。

作为一个极端情况,假设你需要两个函数 print6(const char[6])print12(const char[12]) 来表示 print6("Hello")print12("Hello World") 如果你不想将数组衰减为指针,否则你' d 必须添加一个显式转换,print_p((const char*)"Hello World")

【讨论】:

值得注意的是,其他一些语言确实允许将数组作为参数传递。例如,在 Ada 中,int[5]int[10] 的等价物 (或至少可以是)同一类型;它们只是不同的亚型。您可以定义一个 Ada 例程,它将具有任意边界的整数数组作为参数,或者(对于函数)返回这样的数组。为此付出的代价是编译器必须生成代码来完成所有必要的簿记和内存管理。这种隐式代码通常不被认为是“本着 C 精神”(或 C++)。【参考方案3】:

回答一个非常古老的问题,因为问题是 C++ 的市场,只是为了完成目的而添加,我们可以使用 std::array 并通过值或通过引用将数组传递给函数,从而防止访问超出范围的索引:

以下是示例:

#include <iostream>
#include <array>

//pass array by reference
template<size_t N>
void fill_array(std::array<int, N>& arr)
    for(int idx = 0; idx < arr.size(); ++idx)
        arr[idx] = idx*idx;


//pass array by value
template<size_t N>
void print_array(std::array<int, N> arr)
    for(int idx = 0; idx < arr.size(); ++idx)
        std::cout << arr[idx] << std::endl;


int main()

    std::array<int, 5> arr;
    fill_array(arr);
    print_array(arr);
    //use different size
    std::array<int, 10> arr2;
    fill_array(arr2);
    print_array(arr2);

【讨论】:

【参考方案4】:

您不能按值传递数组的原因是没有特定的方法来跟踪数组的大小,以便函数调用逻辑知道要分配多少内存以及要复制什么。你可以传递一个类实例,因为类有构造函数。数组没有。

【讨论】:

如果数组被声明为a[4],那么你只需在编译时就知道大小。 是的,如果。但是大小不会与数组一起传递,它们不会以允许数组按值传递所需的方式“粘合”在一起。 @quant:在已知的原始范围内,但是 4 在被调用函数中去哪里了? 如果我们特别声明数组的大小。例如,func(int array[20])。仍然无法在 func 中获得大小? @DennisZickefoose @SazzadHissainKhan 也许在 C++ 以外的其他语言中,数组的工作方式与它们在 C++ 中的工作方式非常不同。但是在 C++ 中,您可以将您从 malloc 获得的 char* 传递给需要 int[4] 的函数。还有sizeofdoesn't do what you expect。【参考方案5】:

夏天:

    传递数组第一个元素的地址 &amp;a = a = &amp;(a[0]) 新指针(新指针,新地址,4字节,在内存中) 指向相同的内存位置不同的类型

示例 1:

void by_value(bool* arr) // pointer_value passed by value

    arr[1] = true;
    arr = NULL; // temporary pointer that points to original array


int main()

    bool a[3] = ;
    cout << a[1] << endl; // 0
    by_value(a);
    cout << a[1] << endl; // 1 !!! 

地址:

[main] 
     a = 0046FB18 // **Original**
     &a = 0046FB18 // **Original**
[func]
     arr = 0046FB18 // **Original**
     &arr = 0046FA44 // TempPTR
[func]
     arr = NULL
     &arr = 0046FA44 // TempPTR

示例 2:

void by_value(bool* arr) 

    cout << &arr << arr; // &arr != arr


int main()

    bool a[3] = ;
    cout << &a << a; // &a == a == &a[0]
    by_value(arr);

地址

Prints: 
[main] 0046FB18 = 0046FB18
[func] 0046FA44 != 0046FB18

请注意:

    &(required-lvalue): lvalue -to-> rvalue 数组衰减:新指针(临时)指向(按值)数组地址

阅读全文:

Rvalue

Array Decay

【讨论】:

【参考方案6】:

这样做是为了保持与 B 语言的语法和语义兼容性,其中数组被实现为物理指针。

Dennis Ritchie 的"The Development of the C Language" 直接回答了这个问题,请参阅“评论”部分。它说

例如,函数声明中的空方括号

int f(a) int a[];  ... 

是活化石,是NB声明指针方式的残余; a 仅在这种特殊情况下,在 C 中被解释为指针。该符号之所以得以保留,部分原因是出于兼容性考虑,部分原因在于它允许程序员向他们的读者传达意图传递f一个从数组生成的指针,而不是对单个整数的引用。不幸的是,它既使学习者感到困惑,也使读者警觉。

这应该在文章前一部分的上下文中进行,尤其是“Embryonic C”,它解释了在 C 中引入 struct 类型如何导致拒绝 B 和 BCPL 样式的方法来实现数组(即作为普通指针)。 C 切换到非指针数组实现,仅在函数参数列表中保留旧的 B 样式语义。

因此,当前数组参数行为的变体是一种妥协的结果:一方面,我们必须在 structs 中有可复制的数组,另一方面,我们希望保持与编写的函数的语义兼容性在 B 中,数组总是“按指针”传递。

【讨论】:

【参考方案7】:

相当于首先制作数组的副本,然后将其传递给函数(这对于大型数组可能非常低效)。

除此之外,我会说这是出于历史原因,即无法在 C 中按值传递数组。

我的猜测是,在 C++ 中不引入按值传递数组的原因是,与数组相比,对象被认为是中等大小的。

正如 delnan 所指出的,当使用std::vector 时,您实际上可以按值将类似数组的对象传递给函数。

【讨论】:

【参考方案8】:

正在按值传递:指向数组的指针的值。请记住,在 C 中使用方括号表示法只是取消引用指针的简写。 ptr[2] 表示 *(ptr+2)。

去掉括号会得到一个指向数组的指针,它可以按值传递给函数:

int x[2] = 1, 2;
int result;
result = DoSomething(x);

请参阅 ANSI C 规范中的 list of types。数组不是原始类型,而是由指针和运算符的组合构成的。 (它不会让我放另一个链接,但构造在“数组类型推导”下描述。)

【讨论】:

您没有传递数组的地址,而是传递了数组第一个元素的地址(相同的内存位置,不同的类型)。根据定义,数组索引操作是指针算术和一元 * 解引用运算符的组合,但数组本身只是一个数组。使数组在 C 中不如一等类型的原因不是数组对象本身,而是对它们的有限操作集。 您将数组作为编程概念讨论:从程序员的角度来看的数据结构。我认为问题是为什么数组数据的 C 语法似乎与其他数据不同。如果您将其视为语言结构的问题,那是因为数组不是原语。它们是指针操作,其行为几乎类似于原语。 对数组的大多数操作都是作为指针操作实现的。数组不是指针。例如,数组对象就是数组对象,定义数组对象并不会显式或隐式地创建任何指针对象。 (而且我完全不确定我是否理解您在“数据结构”和“语言结构”之间做出的区别。) 我并不是说数组是指针。但是 C 中的“数组类型”只是被打扮成数据类型的数组运算符。结果是一个令人信服的模仿,但它并不完美。 :) 正如您所提到的,一个怪癖是数组本身没有地址。 @KeithThompson 至于区别,这是询问“幕后”发生的事情与询问“为什么会这样”之间的区别。【参考方案9】:

其实数组的指针是传值的,在被调用的函数中使用这个指针会让你感觉数组是传引用的,这是错误的。尝试将数组指针中的值更改为指向函数中的另一个数组,您会发现原始数组没有受到影响,这意味着该数组不是通过引用传递的。

【讨论】:

以上是关于为啥我们不能通过值传递数组来函数?的主要内容,如果未能解决你的问题,请参考以下文章

调用函数时为啥形参的值不能传给实参

如果 Swift 通过值而不是引用传递,为啥我可以通过将 UIView 传递给函数来在 Swift 中操作 UIView 的属性? [复制]

为啥指针不能传递正确的值(C)?

为啥不允许将数组按值传递给 C 和 C++ 中的函数?

C语言中,数组名作为函数参数,属于啥传递,为啥?

数组名作为函数的参数属于啥传递为啥