27

似乎在大多数主流编程语言中,从一个函数返回多个值是一件非常尴尬的事情。

典型的解决方案是创建一个结构或一个普通的旧数据并返回它,或者通过引用或指针传递至少一些参数而不是返回它们。

使用引用/指针非常尴尬,因为它依赖于副作用并且意味着您还有另一个参数要传递。

恕我直言,类/结构解决方案也很尴尬,因为您最终会得到一百万个仅用于从函数返回值的小类/结构,从而产生不必要的混乱和冗长。

此外,很多时候总是需要一个返回值,其余的仅在某些情况下由调用者使用。这些解决方案都不允许调用者忽略不需要的返回类型。

我所知道的可以优雅地处理多个返回值的一种语言是 Python。对于那些不熟悉的人,它使用元组解包:

a, b = foo(c)  # a and b are regular variables.
myTuple = foo(c)  # myTuple is a tuple of (a, b)

有没有人对这个问题有任何其他好的解决方案?欢迎使用除 Python 之外的现有主流语言和您在非主流语言中看到的语言级解决方案的两种习语。

4

14 回答 14

24

几乎所有受 ML 影响的函数式语言(其中大部分)也有很好的元组支持,这使得这类事情变得微不足道。

对于 C++,我喜欢 boost::tuple 加上 boost::tie (或者 std::tr1 如果你有的话)

typedef boost::tuple<double,double,double> XYZ;

XYZ foo();

double x,y,z;
boost::tie(x,y,z) = foo();

或者一个不那么做作的例子

MyMultimap::iterator lower,upper;
boost::tie(lower,upper) = some_map.equal_range(key);
于 2009-02-05T00:48:27.360 回答
9

一些语言,尤其是 Lisp 和 JavaScript,具有称为解构赋值或解构绑定的特性。这本质上是类固醇上的元组解包:您可以在赋值语句中解包更复杂的对象结构,而不是仅限于像元组、列表或生成器这样的序列。有关更多详细信息,请参见此处了解 Lisp 版本此处了解(更易读的)JavaScript 版本

除此之外,我不知道通常处理多个返回值的许多语言功能。但是,多个返回值有一些特定用途,通常可以被其他语言功能替换。例如,如果其中一个值是错误代码,则最好将其替换为异常。

虽然创建新类来保存多个返回值感觉很混乱,但将这些值一起返回的事实通常表明,一旦创建了类,您的代码总体上会更好。特别是,处理相同数据的其他函数可以移至新类,这可能使您的代码更易于遵循。这并非普遍正确,但值得考虑。(Cpeterso 关于数据块的回答更详细地表达了这一点)。

于 2009-02-05T01:05:05.207 回答
7

PHP 示例:

function my_funct() {
    $x = "hello";
    $y = "world";
    return array($x, $y);
}

然后,运行时:

list($x, $y) = my_funct();
echo $x.' '.$y; // "hello world"
于 2009-02-05T02:31:32.987 回答
5

如果一个函数返回多个值,这表明您可能正在目睹“Data Clump”代码异味。通常,数据块是没有人认为会变成对象的原始值,但是当您开始寻找移动到新对象中的行为时,就会发生有趣的事情。

编写微小的帮助类可能需要额外的输入,但它提供了清晰的名称和强大的类型检查。维护您的代码的开发人员将不胜感激。有用的小类通常会在其他代码中使用。

此外,如果一个函数返回多个值,那么它可能会做太多的工作。函数可以重构为两个(或更多)小函数吗?

于 2009-02-05T00:57:53.910 回答
4

似乎还没有人提到 Perl。

sub myfunc {
  return 1, 2;
}
my($val1, $val2) = myfunc();
于 2009-02-05T01:09:13.543 回答
4

至于 Java,请参阅 Bruce Eckel 的Thinking in Java以获得一个不错的解决方案(第 621 页)。

本质上,您可以定义一个等价于以下内容的类:

public class Pair<T,U> {
    public final T left;
    public final U right;
    public Pair (T t, U u) { left = t; right = u; }
}

然后,您可以将其用作函数的返回类型,并带有适当的类型参数:

public Pair<String,Integer> getAnswer() {
    return new Pair<String,Integer>("the universe", 42);
}

调用该函数后:

Pair<String,Integer> myPair = getAnswer();

您可以参考myPair.leftmyPair.right访问组成值。

还有其他语法糖选项,但以上是重点。

于 2009-02-05T04:17:55.910 回答
3

我认为python是最自然的方式,当我不得不在php中做同样的事情时,唯一的是将return包装到一个数组中。它确实有类似的拆包。

于 2009-02-05T00:43:33.923 回答
2

即使你忽略了Moss Collum 提到的精彩的较新的解构赋值,JavaScript 在返回多个结果方面也相当不错。

function give7and5() {
  return {x:7,y:5};
}

a=give7and5();
console.log(a.x,a.y);

7  5
于 2009-02-05T04:02:06.970 回答
1

通常在 php 中我会使用一个引用的输入:

public function Validates(&$errors=array()) {
   if($failstest) {
      $errors[] = "It failed";
      return false;
   } else {
      return true;
   }
}

这给了我想要的错误消息而不会污染 bool 返回,所以我仍然可以这样做:

if($this->Validates()) ...
于 2009-02-05T00:48:04.787 回答
1

Lua和 CLU(麻省理工学院 Barbara Liskov 的小组)对所有函数都有多个返回值——它始终是默认值我相信 Lua 的设计师受到了 CLU 的启发。我喜欢将多个值作为调用和返回的默认值;我认为这是一种非常优雅的思考方式。在 Lua 和 CLU 中,这种机制完全独立于元组和/或记录。

可移植汇编语言C--支持其本机调用约定的多个返回值,但不支持 C 调用约定。那里的想法是使编译器编写者能够在单个硬件寄存器中返回多个值,而不必将值放在内存中的一个块中并返回一个指向该块的指针(或者像在 C 中一样,让调用者将一个指针传递给一个空丛)。

像任何机制一样,可以滥用多个返回值,但我认为让函数返回多个值比让结构包含多个值更不合理。

于 2009-02-05T05:16:39.440 回答
0

C++您可以将容器传递给函数,以便函数可以填充它:

void getValues(std::vector<int>* result){
  result->push_back(2);
  result->push_back(6);
  result->push_back(73);
}

您还可以让函数将智能指针(使用shared_ptr)返回到 a vector

boost::shared_ptr< std::vector<int> > getValues(){
  boost::shared_ptr< std::vector<int> > vec(new std::vector<int>(3));
  (*vec)[0] = 2;
  (*vec)[1] = 6;
  (*vec)[2] = 73;
  return vec; 
}
于 2009-02-05T03:31:56.983 回答
0

C#

public struct tStruct
{
    int x;
    int y;
    string text;
    public tStruct(int nx, int ny, string stext)
    {
         this.x = nx;
         this.y = ny;
         this.text = stext;
    }
}

public tStruct YourFunction()
{
    return new tStruct(50, 100, "hello world");
}

public void YourPrintFunction(string sMessage)
{
    Console.WriteLine(sMessage);
}

在 Lua 你打电话

MyVar = YourFunction();
YourPrintfFunction(MyVar.x);
YourPrintfFunction(MyVar.y);
YourPrintfFunction(MyVar.text);

输出:50 100 hello world

保罗

于 2010-07-02T07:23:29.597 回答
0

顺便说一句,Scala 可以返回多个值,如下所示(从解释器会话中粘贴):

scala> def return2 = (1,2)
return2: (Int, Int)

scala> val (a1,a2) = return2
a1: Int = 1
a2: Int = 2

这是使用模式匹配进行赋值的一种特殊情况。

于 2011-02-02T00:39:36.680 回答
0

这是我的 Ruby 想法,它返回一个看起来和行为都像标量但实际上具有隐藏属性的特殊值:

https://gist.github.com/2305169

于 2012-04-04T20:03:10.147 回答