在C++和Java中只有一个返回值的原因是什么?

Joo*_*oop 30 c++ java

通常我会看一眼这个事实并接受它为"这就是轮子旋转的方式",但今天我想知道它来自何处:为什么一个函数只有一个返回(参考)值?

为什么不能,函数返回多个值会变得困难或不明智?是因为对象以及您应该如何期望始终以对象的引用形式返回数据包?

如果你看到一个声明函数作为一个契约,它说明了如何调用一个函数,它应该采用哪些参数,以及它具有哪种返回值类型,那么我没有看到背后的逻辑只有一个返回值,因为你可以申请相反的逻辑相反(因此创建了多个返回值的合约).

基于我可以看到的逻辑的唯一原因是,如果一个函数返回多于一个的东西,它还应该操作多于1个的东西超过1个原因,这违背了一个函数应该只做一件事的哲学.如果有一件事是操纵一个对象并返回另一个对象那么它就有意义了,你可以用操纵对象返回一个引用值.

那么为什么存在这种限制呢?

小智 33

每当你有多个值x1...时xN,你(x1, ..., xN)通常会得到一个单元(虽然是复合)值的元组.如果你没有元组你的语言,使用任何聚合类型(struct,class随意,数组,其他收藏品).因此,从这个角度来看,返回"多个"值并返回单个值是完全等效的.

这只是意味着你只需要一个,但为什么要省略另一个呢?

首先,您无论如何都需要聚合类型,因此选择是在两者之间还是仅具有聚合类型.其次,如果一个函数可以返回"多个值",那么你将面临一个语义难题:突然一个表达式没有评估为值(我们之前已经非常具体地定义过),它就是这个新的,不同类别的事物,称为"多个值".现在这个结果的静态类型是什么?该计划可以做什么而不做呢?实施条款意味着什么?

当然,你可以人为地回答这些问题,但任何合理的方法都只会构成元组类型.忽略这一点会让你对一个非常有用的观点视而不见,拒绝让它们成为一等价值可能比说"这些是元组类型复杂,它们可以像这样构造并像这样解构".

  • 关于表达式评估值(+1)的好处. (2认同)
  • LISP定义了一个简单的规则来解决你的难题:第一个值用作表达式的**值; 要访问更多值,您需要一个明确的赋值习惯用法.这适用于许多情况,其中返回值的相关性有明确的层次结构.然而,返回的额外值可能是避免重复昂贵工作的关键.典型示例:DIV返回(商,余数)和MOD返回(余数,商).当您需要两者时,您不希望执行两次昂贵的除法算法. (2认同)

das*_*ght 7

当一个函数返回多个东西时,这些东西彼此相关,至少由于单个函数返回.单独考虑就足以要求返回多个事物的函数的作者通过创建适当的数据结构将这些多个事物组合在一起,这对于返回单个项目的函数已经是可能的.

例如,在C++中std::set::insert需要返回两个值 - 一个迭代器,以及一个bool指示迭代器是一些旧元素还是我们刚刚插入的元素.解决方案是返回std::pair:

pair<iterator,bool> insert (const value_type& val);
Run Code Online (Sandbox Code Playgroud)

第二个考虑因素纯粹是实用的:在表达式中处理多个返回值

int x = f(y);
Run Code Online (Sandbox Code Playgroud)

当只有一个返回值时,它简单直观.尝试返回多个值,如

int x, y = f(w, z);
Run Code Online (Sandbox Code Playgroud)

将变得难以解析,因为读者必须检查返回类型f并查看它是否返回一个int或两个int.

当返回项目的数据类型不同时,我甚至都没有触及这种情况!语法很快就会成为一场噩梦,没有相应的语言表达能力.

  • 如果通过以不同的方式编写"解包多个返回类型"(例如,`(int x,int y)= f(w,z)`)或删除`int,则第二个是无法解决语法歧义的问题x,y;`声明多个变量的语法. (6认同)
  • @delnan肯定有一种方法可以使语法"起作用".然而,使它工作的价格可能太高了. (3认同)
  • 你回复的第一段让我很奇怪.如果可以这样说,也可以这样说:*当一个函数消耗多个东西时,这些东西彼此相关,至少由于被单个函数消耗.仅考虑这一点就足以要求消费多种功能的作者通过创建适当的数据结构将这些多个事物组合在一起.* (3认同)
  • 你的`insert`返回一个`pair`(本质上是一个2元组)的例子只显示你的参数的弱点,因为元组基本上用于语义无关事物的ad-hoc分组.因此,它只不过是克服语法限制的技巧. (2认同)

lef*_*out 7

我希望人们停止解读"我不能用语言YX ",因为"语言Y有一个限制,阻止我做X ".有时这实际上是正确的 - 例如C++' 修饰符基本上只是禁止你以一种语法方式改变值,可以很好地表达,但如果你想确保你的程序行为,那就不认为是明智的以某种可预测的方式.const

OTOH,对于大多数功能X你可以想出它只是没有任何意义,希望他们在一种语言.如果我问,"我不能仅仅区分所有C++函数的原因是什么?"对于一个对编程一无所知的物理学家或工程师来说,这似乎不是一个牵强附会的东西,因为这些人传统上只是在数学意义上处理1 特定类型函数,可以随时根据需要进行区分.

但对于编程语言中的大多数函数而言,从概念上讲,采用衍生产品绝对没有任何意义.对于程序员来说,分析函数只是所有函数的一小部分,仅仅因为导数的概念只适用于连续域(在实践中,基本上适用于向量空间)2,当然它与方面完全不兼容 -允许在程序语言和OO语言中具有"功能"的效果.

现在,允许返回多个值肯定比允许区分函数更加平凡,但它仍然以"函数" 的含义完全改变.你不能再仅仅参考函数返回类型了,你需要弄清楚用多个返回值组合函数意味着什么,你需要为函数指针等提出一些新的语法.等等
差异化例如,所有这一切都可能成为可能,但应该清楚它会改变几乎所有的语言.这不仅仅是"阻止我回复多件事!"

某些语言(例如Fortran和Matlab)对语言中的多个返回值具有硬连线支持.哦,小伙子,我可以告诉你这不会使这些语言的编程更好:你可以很容易地用C++函数做的许多事情都被阻止了,因为它们会被多次返回搞砸!
其他语言,例如Python,有一个轻量级替代方案:看起来你正在返回多个东西,但实际上你只是返回一个元组并获得一些语法糖,用于将组件模式匹配回单个变量.

当然,正如其他答案所说,返回元组或类似的东西是对你的问题的合理解决方案,尽管它有时会在对元组没有很好的语法支持的语言中有点笨拙.(但这只是一些无害的样板.)


1 好吧,至少他们假装他们正在处理分析功能.通常,这在数学上几乎不可接受.实际上,它们只是通过分析函数来逼近一切.

2 有趣的是,在具有适当代数数据类型的语言中,与经典分化思想的显着模拟以非常令人惊讶的方式出现,而实际上并不需要连续的域.这仍然与例如"取x 2 /(1 + e x)的衍生物"完全不同.


更进一步:可以说,函数也应该只有一个参数.那也可以是复合类型,但你甚至不需要元组:有一个名为Currying的东西- 两个参数的任何函数也可以表示为单个参数的函数,返回第二个参数的函数.这可能看起来有点像一种奇怪的思维方式,但实际上它的效果非常好.因此,Haskell就是这样做的,许多人认为它是关于语言的疯狂事情之一,但是它允许你比大多数其他语言更可靠地表达一些操作,同时语法实际上保持非常简单.


luk*_*k32 6

这纯粹是历史性的.这通常是数学函数的工作方式.而且,一些语言(实际上很多)允许返回多个值 - 通常使用元组对象.

返回多个对象会很好的想法迟到了.为什么选择通过对象完成,而不是语言语法?因为OO编程在野外使用并且被普遍接受.

语义上两种方式都有相同的含义 - "不止一件事".因此,添加此类功能将等同于支持具有相同含义的另一种语法.有两种方法可以做一件事在编程中不是很受欢迎.程序员想知道选择哪种方式,有些只知道一种方法,编译器开发人员必须维护两种功能没有区别的机制.这是一个非常好的理由.Okham的剃须刀是受欢迎的工具.对象更有用.

另一件事是,你的论点:" 一个声明的函数作为一个契约,它说明了如何调用一个函数,它应该采用哪些参数,以及它具有哪种返回值类型 ",并不是很擅长于粗体部分.

返回类型通常不被视为函数签名的一部分.您不能只有返回值不同的重载.话虽这么说,当然编译器需要知道在函数完成后它应该占用多少字节作为返回值.所以它并非完全不相关,尽管通常语言不考虑这一点.


Mar*_*nik 6

你的问题并没有说明你正在寻找哪个确切的特征:语法糖使它"看起来"就像你正在返回几个值,或者函数返回多个值的基本能力.

如果它是前者,那么就没有什么可以给出的答案:语言只是他们为此提供的语法支持水平不同.例如,LISP完全支持多值返回,并且许多语言(Scala,Clojure)支持与您正在寻找的非常类似的解构绑定习惯,但是对着引用类型的元组起作用.Java在语法方面一般都很差,因此缺乏对解构绑定的支持也就不足为奇了.

至于后者 - 在C中,返回一个struct类型正是您正在寻找的功能.结构将放在堆栈框架上,并包含任意数量的值.其他语言,不允许您在这样的细节级别控制堆栈/堆放置,仍允许您返回指向此类结构的指针.


jws*_*jws 5

回到过去,某些 CPU 寄存器具有特定的约定,并且 CPU 寄存器是有限的。因此,汇编代码通常只返回一个值,例如,在 8086 上的 AX 中。

编写汇编代码很费力,而“C”的出现减轻了一些负担,同时仍然允许开发人员实现与汇编代码类似的性能。

为了强调这一点,请考虑以下“C”代码:

int foo(void)
{
    // no return statement; default register used
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,函数 foo 将返回 CPU 类型的“返回值”寄存器中的值,例如,32 位 x86 处理器上的 EAX。

'C++' 可与 'C' 互操作,并且不引入第二种形式的 return 语句。