【问题标题】:Why doesn't C++ support functions returning arrays?为什么 C++ 不支持返回数组的函数?
【发布时间】:2011-07-06 15:49:07
【问题描述】:

有些语言允许您像 Java 一样声明一个返回数组的函数,就像普通函数一样:

public String[] funcarray() {
   String[] test = new String[]{"hi", "hello"};
   return test;
}

为什么 C++ 不支持 int[] funcarray(){} 之类的东西? 你可以返回一个数组,但是做这样一个函数真的很麻烦。而且,我在某处听说字符串只是 char 数组。那么如果你可以在 C++ 中返回一个字符串,为什么不能返回一个数组呢?

【问题讨论】:

  • 为什么不使用指针创建数组然后返回指针?
  • @RageD 当然,你可以这样做。但是 C++ 的创建者难道不能让数组返回函数自动完成而不打扰程序员吗?
  • @MisterSir:我想说这更像是一个特性——它允许一致性。如果您使用指针创建数组,则您已在堆上动态分配内存 - 也就是说,您可以通过引用返回并消除任何复制开销(因此数组的大小不会影响效率)。但是,您确实需要记住释放已分配的内存。
  • @MisterSir - 而且,它不会困扰程序员。 C 和 C++ 不是应用程序编程语言。它们是系统编程语言。因此,这些语言中的设计决策反映了预期的工作类型。别以为高大上。认为低水平。走低,下到金属。回顾我们在汇编、计算机组织和操作系统方面学到的东西。然后,当涉及到 C 和 C++ 时,事情就会开始变得更有意义。
  • @luis.espinal:“C 和 C++ 不是应用程序编程语言。它们是系统编程语言。[...] 不要想高水平。” - 它们被大量使用并且非常适合两者(C当然显示它的年龄)。您关于系统编程历史和使用有助于理解的观点是有效的,但不是暗示任何一种语言都不适合或不适合高级/应用程序编程。

标签: c++ arrays function


【解决方案1】:

这些答案都没有抓住重点。 C++ 只是不支持它。它甚至不支持在std::array<T, N> 之前返回静态大小的数组的方法。 C++可以支持返回动态大小的数组,但它们不支持。我确信有正当理由,但他们可以。

您需要做的就是在堆栈上分配动态数组,返回它的地址和大小,并确保调用者将堆栈指针撞到返回数组的末尾。可能需要修复一些堆栈帧,但绝不是不可能的。

【讨论】:

    【解决方案2】:

    我敢打赌,简而言之,这只是一个设计决定。更具体地说,如果您真的想知道原因,您需要从头开始工作。

    让我们首先考虑 C。在 C 语言中,“按引用传递”和“按值传递”之间有明显的区别。轻描淡写,C 中的数组名其实只是一个指针。出于所有意图和目的,差异(通常)归结为分配。代码

    int array[n];
    

    将在堆栈上创建 4*n 字节的内存(在 32 位系统上),与作出声明的代码块的范围相关。反过来,

    int* array = (int*) malloc(sizeof(int)*n);
    

    将创建相同数量的内存,但在堆上。在这种情况下,该内存中的内容与范围无关,只有对内存的引用受范围限制。这就是按值传递和按引用传递的用武之地。您可能知道,按值传递意味着当某个函数传入或从函数返回时,传递的“事物”是对变量求值的结果。换句话说,

    int n = 4;
    printf("%d", n);
    

    将打印数字 4,因为构造 n 的计算结果为 4(对不起,如果这是基本的,我只想涵盖所有基础)。这 4 与您的程序的内存空间绝对没有任何关系或关系,它只是一个文字,因此一旦您离开该 4 具有上下文的范围,您就会失去它。通过引用传递呢?通过引用传递在函数的上下文中没有什么不同。您只需评估通过的构造。唯一的区别是,在评估传递的“事物”之后,您将评估结果用作内存地址。我曾经有一位特别愤世嫉俗的 CS 教练,他喜欢说没有通过引用传递这样的事情,只是传递聪明价值观的一种方式。真的,他是对的。所以现在我们从函数的角度来考虑范围。假设你可以有一个数组返回类型:

    int[] foo(args){
        result[n];
        // Some code
        return result;
    }
    

    这里的问题是结果计算为数组的第 0 个元素的地址。但是当您尝试从该函数外部(通过返回值)访问此内存时,您会遇到问题,因为您正在尝试访问不在您正在使用的范围内的内存(函数调用的堆栈)。所以我们解决这个问题的方法是使用标准的“通过引用”jiggery-pokery:

    int* foo(args){
        int* result = (int*) malloc(sizeof(int)*n));
        // Some code
        return result;
    }
    

    我们仍然得到一个指向数组第 0 个元素的内存地址,但现在我们可以访问该内存了。

    我的意思是什么?在 Java 中,通常断言“一切都是按值传递”。这是真实的。上面这位愤世嫉俗的导师也对 Java 和 OOP 有这样的看法:一切都只是一个指针。他也是对的。尽管 Java 中的所有内容实际上都是按值传递的,但几乎所有这些值实际上都是内存地址。因此,在 Java 中,该语言确实允许您返回一个数组或一个字符串,但它是通过将其转换为带有指针的版本来实现的。它还为您管理您的记忆。自动内存管理虽然有用,但效率不高。

    这将我们带到了 C++。发明 C++ 的全部原因是 Bjarne Stroustrup 在他的博士工作期间一直在试验 Simula(基本上是最初的 OOPL),并认为它在概念上非常棒,但他注意到它的性能相当糟糕。于是他开始研究所谓的 C with Classes,后来重命名为 C++。在这样做的过程中,他的目标是制作一种编程语言,该语言吸收了 Simula 的一些最佳功能,但仍保持强大和快速。他选择扩展 C 是因为其已经具有传奇色彩的性能,而一个权衡是他选择不像其他 OOPL 那样大规模地实现自动内存管理或垃圾收集。从一个模板类返回一个数组是可行的,因为,你正在使用一个类。但是如果你想返回一个 C 数组,你必须用 C 的方式来做。换句话说,C++ 确实支持与 Java 完全相同的方式返回一个数组;它只是不能为你完成所有的工作。因为一个丹麦人认为它太慢了。

    【讨论】:

    • 另外,作为对“Stings are arrays of characters”评论的回应;这大多是真的。在 C 中,没有 String 类型这样的东西。你自己处理。它们存储在字符数组中,以空值结尾,虽然存在一个字符串库来执行诸如查找长度之类的操作,但它是通过解析字符串来完成的。 C++ 或 Java 中的 String 可以被认为是一个包含字符数组的类,但也包含其他成员字段,这些字段维护有关数组的信息,例如长度,因此更容易操作。所以回到通过引用传递。
    • 这正是我正在寻找的答案!也大大提高了我对记忆的理解。谢谢!
    • 不会再... 数组和指针是不同的野兽 这种类型的答案,即使有 轻视它 限定符也只能添加到混乱。
    • 我也从未说过数组是指针。我说数组的名称是一个指针。这虽然在语义上非常错误,但只是一种简短且非技术性的说法,即除了在非常特殊的情况下,类型 T 的数组的名称将衰减为指向第一个元素的类型 T 的指针,尽管不用说,数组的名称是不可修改的左值。但还是很抱歉。我理解您的担忧。
    • 这应该被提名为某种很棒的答案奖。我刚刚学到了一大堆东西,因为它重新排列了我一直都知道并认为理所当然的东西。
    【解决方案3】:

    C++ 确实支持它——很好:

    vector< string> func()
    {
       vector<string> res;
       res.push_back( "hello" );
       res.push_back( "world" );
       return res;
    }
    

    甚至 C 也支持它:

    struct somearray
    {
      struct somestruct d[50];
    };
    
    struct somearray func()
    {
       struct somearray res;
       for( int i = 0; i < 50; ++i )
       {
          res.d[i] = whatever;
       }
       // fill them all in
       return res;
    }
    

    std::string 是一个类,但是当你说一个字符串时,你可能指的是文字。您可以从函数中安全地返回文字,但实际上您可以静态创建任何数组并从函数中返回它。如果它是一个 const(只读)数组,这将是线程安全的,字符串文字就是这种情况。

    但是,您返回的数组会降级为指针,因此您无法仅从它的返回中计算出它的大小。

    如果可能的话,返回一个数组首先必须是固定长度的,因为编译器需要创建调用堆栈,然后存在数组不是左值的问题,所以接收它调用函数必须使用带有初始化的新变量,这是不切实际的。出于同样的原因,返回一个也可能不切实际,尽管他们可能对返回值使用了特殊的表示法。

    请记住,在 C 语言的早期,所有变量都必须在函数顶部声明,而您不能只在第一次使用时声明。因此在当时是不可行的。

    他们提供了将数组放入结构的解决方法,这就是它现在必须保留在 C++ 中的方式,因为它使用相同的调用约定。

    注意:在像 Java 这样的语言中,数组就是一个类。你用新的创建一个。您可以重新分配它们(它们是左值)。

    【讨论】:

    • 如果数组的大小在编译时是固定的,可以使用时间 std::array (或 std::tr1::array 或 boost ::array).
    • A std::vector 不是数组,也不是包含数组的结构。这些只是解决返回数组限制的简单机制(实际的本机类型,而不是它的结构或对象包装器)。我了解您的用途,这些都是可行的示例。但是,这些既不是 C++(或 C)支持的特性(返回 本机类型数组)的示例,也不是解释 C++ 中存在限制的原因。
    • @luis C++ 使用与 C 相同的调用约定。数组不是 C 或 C++ 中的左值,这是主要问题。
    • 您的示例仍然返回一个指向本地内存的无效指针——如果没有复制构造函数进行深度复制,则返回值的“d”成员将与局部变量“res”,指向栈上不再存在的内存。
    • @v.oddou 但是数组不能从指针隐式构造。 “数组”函数 parameter 不是数组,它是一个指针。它可以看起来像一个数组来迷惑人们(有人可能在 60 年代后期的某个时候认为这是一个好主意。)
    【解决方案4】:

    "你不能从 函数,因为该数组将是 在函数内部声明,以及它的 位置将是堆栈 框架。但是,堆栈帧被删除 当函数退出时。函数必须 将返回值从堆栈帧复制到 返回位置,那不是 可以使用数组。”

    来自这里的讨论:

    http://forum.codecall.net/c-c/32457-function-return-array-c.html

    【讨论】:

    • 拒绝从您引用的链接中逐字复制。此外,这个答案具有误导性。特别是“函数必须复制返回值 [原文如此]”在技术上是错误的,因为函数可以返回引用和指针。
    • 我没有看到引用有问题,链接了参考。
    • @phooji:引用和指针都是指针,它们本身都是值。如果您了解指针是什么,就不会产生误导。
    • @Orbit:如果你想让你的答案看起来像你在引用,那么在“你不能用数组返回 [...]”周围使用引号(看看我在那里做了什么? ;) 仅添加链接是不够的,因为有人可能仍然声称您“窃取”了他们的文本;使用引号很明显您正在使用其他人的文本。
    • 我不能同意这个答案。对于大多数其他类型,您可以按值返回,并且返回的对象在函数内部这一事实没有问题:制作了一个副本(如果编译器设法这样做,则将其删除)。这是一种常见的行为,而数组无法做到这一点的事实更多地是 C 语言中的设计决策——继承于 C++。事实上,如果你将数组包含在一个结构体中,这正是会发生的情况:结构体(包括内部数组)将被复制到 return 语句中。
    【解决方案5】:

    其他人说在 C++ 中,使用向量而不是从 C 继承的数组。

    那么为什么 C++ 不允许返回 C 数组呢?因为 C 没有。

    为什么 C 没有?因为 C 是从 B 演变而来的,B 是一种无类型语言,其中返回一个数组根本没有意义。在向 B 添加类型时,可以返回一个数组本来是有意义的,但这并不是为了保持一些 B 惯用语的有效性并简化程序从 B 到 C 的转换。从那时起,可能性让 C 数组更可用,但一直被拒绝(甚至更多,甚至没有考虑),因为它会破坏太多现有代码。

    【讨论】:

    • “使 C 数组更可用......会破坏太多现有代码” - 不正确。如果现有程序包含返回数组的函数,则它们将不会编译,因此此类功能仅与选择使用这些函数的新代码相关,并且绝不会使现有代码无效。换句话说,您并不是在假设改变现有行为,而是假设新的独立行为。
    • @TonyD,您要么需要删除数组自动衰减为指针,这会破坏大量代码,要么会产生很多特殊情况,以至于您没有制作 C 数组更有用,或者改变的东西太少以至于不值得痛苦。
    • 有趣的断言。请帮助我了解您的具体问题。对于上下文,请考虑int[4] f() { int x[4]; ...populate x...; return x; } 并以直观的方式使其有用,让我们在返回和ala int x[4] = f(); 中添加对数组赋值的新支持。我看不出这将如何需要指针衰减,也不需要更改其他代码来防止指针衰减。您认为哪种代码与此冲突?
    • @tonyd,如果您不更改当前规则,f() 的结果将衰减为指针(就像 int (*p)[4]*p 衰减为指针一样)。跨度>
    • 但它什么时候会衰减? - 只有当原始类型无法分配时,它才会衰减。很像long x = get_char(); - 仅尝试转换为long,因为赋值的rhs 操作数还不是long。所以,我们谈论的不是对指针衰减的某种抑制,而是在考虑之前有一些新的工作。 “(就像 int (*p)[4], *p 衰减成一个指针)” - 不是这样,*p 仍然是 int[4] - 通过传递给 template &lt;int N&gt; void f(int (&amp;a)[N]) { std::cout &lt;&lt; N &lt;&lt; '\n'; } 来确认。腐烂是最后的手段。
    【解决方案6】:

    “为什么 C++ 不支持类似的东西”:因为它没有任何意义。在 JAVA 或 PHP 等基于引用的语言中,内存管理基于垃圾收集。没有引用的内存部分(程序中的变量不再指向它)会被自动释放。在这种情况下,您可以分配内存,并轻松地传递引用。

    C++代码会被翻译成机器码,里面没有定义GC。因此,在 C 和 C++ 中,对内存块有一种强烈的所有权意识。你必须知道你去的指针是否是你随时释放的(实际上你应该在使用后释放它),或者你有一个指向内存共享部分的指针,这是绝对的禁止免费。

    在这种环境中,每次数组传入和传出函数时都创建无穷无尽的数组副本,这将一无所获。用类 c 语言管理数据数组要复杂得多。没有万能的解决方案,您需要知道何时释放内存。

    函数返回的数组是否总是一个副本(您可以免费使用),还是您必须制作它们的副本?将一个指向数组的指针插入到一个数组中,你会赢吗?

    【讨论】:

    • 为什么返回数组没有意义? C++ 不得不发明std::array,部分是为了克服这个神秘的限制。这与 GC 或引用无关。 C++ 允许您按值返回对象(事实上,C 也是如此。)只是不是普通数组。没有任何意义的是你的答案。
    • 我认为根本问题是,如果方法要按值返回某些内容,则必须在在调用该方法之前保留相关内容的空间。由于可以为此目的将固定大小的数组封装在结构中,并且由于此类结构的行为比数组类型更一致且更有用,因此返回固定大小的数组类型几乎没有什么好处。在某些情况下,可变大小的数组可能很好,但调用者没有合理的机制为它们提供空间。
    【解决方案7】:

    C 中的数组(以及为了向后兼容而在 C++ 中)具有与其他类型不同的特殊语义。特别是,对于其他类型,C 仅具有按值传递语义,在数组的情况下,按值传递语法的效果以一种奇怪的方式模拟了按引用传递:

    在函数签名中,类型为T 类型的N 个元素的数组 类型的参数被转换为指向T 的指针。在函数调用中,将数组作为参数传递给函数会将数组衰减指向第一个元素的指针,然后将该指针复制到函数中。

    由于对数组的这种特殊处理——它们不能按值传递——它们也不能按值返回。 C中可以返回指针,C++中也可以返回引用,但数组本身不能在栈中分配。

    如果您考虑一下,这与您在问题中使用的语言没有什么不同,因为数组是动态分配的,您只返回一个指针/引用。

    另一方面,C++ 语言支持针对特定问题的不同解决方案,例如在当前标准中使用 std::vector(内容是动态分配的)或在即将发布的标准中使用 std::array(内容可以在堆栈,但它可能有更高的成本,因为在编译器无法删除副本的情况下,每个元素都必须被复制)。事实上,您可以通过使用像 boost::array 这样的现成库来使用与当前标准相同类型的方法。

    【讨论】:

    • 关于“在函数签名中,[数组->指针]”“[因此]它们不能按值返回”。 8.3.5.5 确实需要调整“任何类型为“T 数组”的参数以使用指针,但没有声明说该处理适用于返回类型,因为它们是不允许的。您的解释听起来像是对参数的处理应用于返回的类型并产生了一个无效的签名。并非如此 - 简单明了,数组返回类型是不允许的:8.3.5.8“函数不应具有数组或函数类型的返回类型”。
    • @TonyD :我认为他的解释很好,并且比公认的答案更好。最后的 std::vector /array 东西是题外话。 (因为这与使用 RVO/复制省略和返回值语义的东西的语义不同,而不是返回您期望的指向 C 数组的指针,因为“衰减到指针”概念通过每个 C 语言的初学者,因为它是最先学习的东西之一)
    【解决方案8】:

    你可以返回一个指向数组的指针。以后要小心释放内存。

    public std::string* funcarray() {
        std::string* test = new std::string[2];
        test[0] = "hi";
        test[1] = "hello";
        return test;
    }
    
    // somewhere else:
    std::string* arr = funcarray();
    std::cout << arr[0] << " MisterSir" << std::endl;
    delete[] arr;
    

    或者您可以只使用 std 命名空间中的容器之一,例如 std::vector。

    【讨论】:

    • 我不应该也删除 std::string* 测试吗?
    • @MisterSir - 不,没有必要。 test 是一个驻留在堆栈上的变量,在函数返回时超出范围。但是,test 指向的位置位于堆/空闲存储中,并返回到arr。所以,如果你删除arr,就足够了。
    【解决方案9】:
    【解决方案10】:

    返回 std::vector&lt;&gt; 而不是数组。一般来说,数组不适用于 C++,一般应避免使用。

    此外,string 数据类型不仅仅是一个字符数组,尽管“引用字符串”是。 string 管理一个字符数组,您可以使用.c_str() 访问它,但string 的功能远不止这些。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-06-29
      • 2014-03-13
      • 1970-01-01
      • 1970-01-01
      • 2020-09-18
      • 2011-11-22
      • 2015-12-13
      相关资源
      最近更新 更多