为什么我们不能传递数组按值运行?

Why can't we pass arrays to function by value?

本文关键字:运行 数组 我们 不能 为什么      更新时间:2023-10-16

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

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

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

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

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

这里有另一个视角:C中没有单一类型的"数组"。相反,对于每个NT[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"),如果您不想将数组衰减为指针,或者您必须添加显式转换print_p((const char*)"Hello World")

回答一个非常古老的问题,因为问题是市场,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);
}

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

汇总:

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

示例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

请注意:

  1. &(必需的左值(:左值-到->右值
  2. 数组衰退:新指针(临时(指向(按值(数组地址

自述:

R值

阵列衰减

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

这个问题的直接答案在Dennis Ritchie的"C语言的发展"中给出,见"评论家"部分。上面写着

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

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

是活化石,是NB宣布指针的方式的残余;仅在这种特殊情况下,a在C中被解释为指针。这种表示法之所以保留下来,部分是为了兼容性,部分是因为它将允许程序员向读者传达一种意图,即向f传递一个由数组生成的指针,而不是对单个整数的引用。不幸的是,它既能提醒读者,也能迷惑学习者。

这应该放在文章前一部分的上下文中,特别是"胚胎C",它解释了在C中引入struct类型是如何导致拒绝B和BCPL风格的方法来实现数组(即作为普通指针(的。C切换到非指针数组实现,只在函数参数列表中保留传统的B风格语义。

因此,数组参数行为的当前变体是一种折衷的结果:一方面,我们必须在struct中拥有可复制的数组,另一方面,我方希望保持与用B编写的函数的语义兼容性,在B中,数组总是"通过指针"传递的。

等效的方法是首先复制数组,然后将其传递给函数(对于大型数组来说,这可能效率很低(。

除此之外,我想说这是由于历史原因,即不能通过C.中的值传递数组

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

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

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

去掉括号可以得到一个指向数组的指针,该指针可以通过值传递给函数:

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

请参阅ANSI C规范中的类型列表。数组不是基元类型,而是由指针和运算符的组合构建的。(它不会让我再放一个链接,但结构在"数组类型派生"下描述。(

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