大多数编程语言的函数(方法)都只能返回一个值,这种函数也是在数学中的标准定义,如y = f(x),后面的f(x)不管多复杂,y永远只有一个。不过有少数编程语言,函数可以返回多个值,Go和Python就是这样的语言。其实这种返回多值的方式对于有些编程语言,如Python,就是一个语法糖。不过对于go来说,还真需要,那么为什么需要呢?继续往后看吧!

        现在先来看一个返回4个值的calc函数:

package main
func calc(a int, b int) (int, int, int, int) {
    return a + b, a - b, a * b, a / b
}
func main() {
    a, b, c, d := calc(20, 5)
    println(a, b, c, d)
}

        这是一个用于计算a和b两个整数的加、减、乘、除的函数,该函数同时返回了这4个值。在main函数中调用了calc函数。不过要注意,在调用go函数时,用于接收函数返回值的变量个数必须与函数返回值的个数相同,否则无法编译通过,并会显示如下图所示的错误信息。

         将函数的返回值赋给变量,有两种方式,一种就是本例的简化方式,通过“:=”自动创建左侧的变量,并用函数的返回值给变量初始化。第2种方式就比较正常,使用var定义4个变量,可以为变量指定数据类型,也可以省略数据类型(go会根据函数返回值的类型自动推断变量的数据类型),代码如下:

// 省略变量数据类型
var a1, b1, c1, d1 = calc(20, 5)
// 指定变量的数据类型
var a2, b2, c2, d2 int = calc(20, 5)

        到现在为止,我们总算明白了,原来go语言的函数返回多个值,就是要将这些返回值分配给同等数量的变量,而且变量的数据类型和数量必须与函数返回值的数据类型与数量完全相同,多一点不行,少一点也不行。但这样就会带来一个很麻烦的问题。

        Go语言有一个规定,就是你在代码中只要定义了变量,必须要使用,不使用你就别定义。按这个规定,如果某个函数由于业务需要,返回了20个值,那么就意味着在调用这个函数时,在赋值语句的左侧需要连续写20个变量,天哪!变量名都不好起。不过go语言为我们提供了一个比较省事的方式,就是如果函数的某一个返回值没什么用,那么可以用下划线(_)代替,表示这个返回值被忽略了。如前面给出的calc函数,只需要a+b和a-b的值,那么可以用下面的代码调用calc函数。

package main
func calc(a int, b int) (int, int, int, int) {
    return a + b, a - b, a * b, a / b
}
func main() {
    var a, b, _, _ = calc(20, 5)  // 省略了a * b和a / b的值
    println(a, b)
}

        现在调用多返回值函数是没什么问题了,但在编写多返回值函数时还有点小问题,就是如果函数的返回值一多,将所有的返回值都放到return语句后面,可能会弄错,如将顺序弄错,这种错误发生的概率相当高。

        Go语言解决这个问题的方式就是用命名返回值,也就是与函数参数一样,为每一个返回值起一个名字,这样在返回值时,就直接为返回值变量赋值即可,不过仍然需要调用return语句,只是return语句后面可以什么都不返回,现在来改进前面的calc函数。

package main
func calc(a int, b int) (sum int, sub int, mul int, div int) {
    sum = a + b
    sub = a - b
    mul = a * b
    div = a / b
    return
}
func main() {
    var a, b, c, d = calc(20, 5)
    println(a, b, c, d)
}

        看到这段代码是不是很清晰,尽管代码多一点,但绝对不会弄错(除非故意的)。

        当然,就算有命名返回值,return语句也可以返回具体的值,只是会覆盖前面的给命名返回值变量赋的值,代码如下:

package main
func calc(a int, b int) (sum int, sub int, mul int, div int) {
    //return a + b, a - b, a * b, a / b
    sum = a + b
    sub = a - b
    mul = a * b
    div = a / b
    return 2 * sum, 2 * sub, 2 * mul, 2 * div
}
func main() {
    var a, b, c, d = calc(20, 5)
    println(a, b, c, d)
}

        我们看到,在calc函数中,return语句将所有的值以2倍的形式返回,那么最终的输出结果是50 30 200 8 。

        现在来回答本文最开始的提出的问题:Go语言的函数为什么需要返回多个值。

        由于go语言没有try...catch语句,尽管可以通过defer机制处理异常,不过不太好用,所以通常的做法是通过函数返回一个error(有点像C语言处理异常的方式,返回0表示成功,返回非0表示失败)。如果error是nil,表示未发生任何错误,可以正常使用函数的返回值,如果error不为nil,那么说明有错误,需要做进一步地处理。

        基于这个原因,如果go语言的函数不支持返回多值,那么返回error,就不能再返回其他值了,所以从这一点来说,Go支持多返回值函数,也在情理之中。像Python语言是支持try...catch的,所以多返回值函数并不是必须的,当然,支持多返回值函数也会让程序变得更简洁。所以对于Python来说,多返回值函数只是锦上添花,而对于go语言来说,多返回值函数是雪中生态。

        下面就让calc函数再返回一个error。由于除法的分母不能为0,所以如果b为0,就返回error,代码如下:

package main
import "errors"
func calc(a int, b int) (sum int, sub int, mul int, div int, err error) {
    if b == 0 {
        err = errors.New("错误:分母不能为0!")
        return
    }
    sum = a + b
    sub = a - b
    mul = a * b
    div = a / b
    err = nil
    return
}
func main() {
    var a1, b1, c1, d1, err1 = calc(20, 5)
    if err1 == nil {
        println(a1, b1, c1, d1)
    } else {
        println(err1.Error())
    }
    var a2, b2, c2, d2, err2 = calc(20, 0)
    if err2 == nil {
        println(a2, b2, c2, d2)
    } else {
        println(err2.Error())
    }
}

运行程序,会输出如下图所示的内容。