Go语言函数的一个显著特征是支持多个返回值。这使得函数可以同时返回计算结果和错误状态,无需像其他语言那样使用异常或返回包装对象。此外,Go还支持命名返回值,进一步提升代码可读性,并允许使用裸返回(naked return)简化代码。本章将系统讲解这些特性及其最佳实践。
函数定义时在返回值列表中可以列出多个类型,使用逗号分隔,并用括号括起。调用方使用多个变量接收。
package main
import "fmt"
// 返回字符串长度和字符串本身
func getInfo(s string) (int, string) {
return len(s), s
}
// 交换两个整数
func swap(a, b int) (int, int) {
return b, a
}
func main() {
length, str := getInfo("Hello")
fmt.Printf("长度: %d, 内容: %s\n", length, str)
x, y := swap(10, 20)
fmt.Printf("x=%d, y=%d\n", x, y)
}
Go语言惯用法:返回(result, error),其中error表示可能发生的错误。调用方必须检查error是否为nil。
import (
"fmt"
"errors"
)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
res, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", res)
}
}
strconv.Atoi、os.Open等),强制开发者处理错误,提高程序健壮性。可以为返回值指定名称,这样在函数体内相当于预先声明了变量,可以对其进行赋值,并按名称返回。命名返回值有助于文档化,同时允许“裸返回”。
// 命名返回值:area, perimeter
func rectProps(length, width float64) (area, perimeter float64) {
area = length * width
perimeter = 2 * (length + width)
return // 裸返回,自动返回 area 和 perimeter 的当前值
}
func main() {
a, p := rectProps(5.0, 3.0)
fmt.Printf("面积: %.2f, 周长: %.2f\n", a, p)
}
命名返回值在函数开始时会初始化为类型的零值。注意:如果函数中有分支,请确保所有路径都正确设置了命名返回值,否则零值会被返回。
裸返回是指return语句后面不跟任何表达式,直接返回当前的命名返回值。它适用于短小清晰的函数,但在长函数中可能降低可读性。
// 将温度从摄氏度转换为华氏度和开尔文
func convertTemp(celsius float64) (fahrenheit, kelvin float64) {
fahrenheit = celsius*9/5 + 32
kelvin = celsius + 273.15
return // 裸返回
}
func main() {
f, k := convertTemp(100)
fmt.Printf("100°C = %.2f°F = %.2fK\n", f, k)
}
return values更清晰。滥用裸返回会造成“变量从何处返回”的困惑。
即使函数使用了命名返回值,也可以显式返回其他值或表达式,此时会覆盖命名返回值的当前值。但一般不推荐混用。
func weirdFunc(x int) (y int) {
y = x * 2
return 999 // 显式返回,忽略了 y 的值,最终返回 999
}
fmt.Println(weirdFunc(5)) // 999
更好的实践是:要么全部使用命名返回值+裸返回,要么全部使用显式返回。
如果函数返回多个值,但调用方只需要其中部分,可以使用下划线_忽略。
// 假设函数返回 (int, bool)
func getValue() (int, bool) {
return 100, true
}
val, _ := getValue() // 忽略布尔值
fmt.Println(val)
Go的多返回值通过栈或寄存器传递,性能开销很小。不需要像某些语言那样包装成对象或元组。可以放心使用多返回值使代码更清晰。
package main
import "fmt"
type SafeMap struct {
data map[string]int
}
// 返回值命名:value和exists
func (sm *SafeMap) Get(key string) (value int, exists bool) {
value, exists = sm.data[key]
return // 裸返回
}
func main() {
sm := &SafeMap{
data: map[string]int{"apple": 5, "pear": 3},
}
if val, ok := sm.Get("apple"); ok {
fmt.Println("Apple:", val)
} else {
fmt.Println("Apple not found")
}
if val, ok := sm.Get("orange"); ok {
fmt.Println("Orange:", val)
} else {
fmt.Println("Orange not found")
}
}