Go 没有三元运算符,没有 cond ? a : b。惯用的替代写法是 if 语句:
先用默认值声明变量,再在 if 中修改它。两个分支都需要做计算时,用 if/else:
var fee int
if member {
fee = 0
} else {
fee = computeFee(order)
}
Go 为什么不加入它
Go FAQ 对此有直接回答:设计者见过太多用 ?: 运算符写出的复杂难懂的表达式,于是认为 if/else 虽然更长,但无疑更清楚。一门语言只需要一种条件控制流结构。
实际上,缺少这个运算符每次只多写两三行,却消除了 a ? b ? c : d : e 这类嵌套表达式。
改为提前返回
在函数内部,最简洁的写法往往连变量都不需要:
把条件提取到一个有名字的小函数里通常是最好的办法:调用处读起来就像你想要的三元表达式,函数名还说明了条件的含义。
用 cmp.Or 设置默认值(Go 1.22)
其他语言中最常见的三元表达式是“用这个值,如果为空就用备用值”:name ? name : "anonymous",或者 JavaScript 中的 name || "anonymous"。Go 1.22 正是为此加入了 cmp.Or。它返回第一个不是其类型零值的参数:
它把零值当作“缺失”,所以无法区分“端口故意设为 0”和“端口没有设置”。当零值是有效值时,请用指针、单独的 bool 或显式的 if。
泛型 If 辅助函数及其陷阱
有了泛型(Go 1.18),你可以写一个看起来像三元运算符的函数:
func If[T any](cond bool, a, b T) T {
if cond {
return a
}
return b
}
status := If(ok, "pass", "fail")
它对简单的值有效,有些代码库也在用。但它不是三元运算符,而且区别很关键。Go 在调用函数之前会先求值所有参数,所以 a 和 b 总是都会被计算:
第一次调用把 "a" 和 "b" 都计算了,第二次调用即使条件为假,也会因空指针解引用而 panic。真正的三元运算符只会求值被选中的那一边。所以:
- 当某一边要对指针解引用、按下标访问切片,或读取可能不存在的 map 项时,永远不要用这种辅助函数。
- 当某一边开销很大或有副作用时,永远不要用它。
- 对于开销小又安全的值它没问题,但很多 Go 代码审查者仍然会要求你改成普通的
if。
你可以通过传入函数让分支延迟求值,If(ok, func() string { return a }, func() string { return b }),但到了这一步,if 语句反而更短。
其他技巧,以及为什么要避免
map 查找。 map[bool]string{true: "yes", false: "no"}[ok] 可以运行,但每次求值都要构建一个 map 并做哈希查找,而且两个值都会被求值。它只是个奇技淫巧,不是惯用写法。
立即调用的函数。 定义后立即调用的匿名函数可以给你一个表达式:
label := func() string {
if n > 0 {
return "positive"
}
return "non-positive"
}()
它确实是延迟求值的,在不能写语句的复合字面量里偶尔有用。大多数时候,在字面量之前先把值算好存进变量会更清楚。
选择替代写法
| 情况 | 用法 |
|---|---|
| 在两个值中二选一 | 先赋默认值,再用 if |
| 两个分支都需要计算 | if/else |
| 值决定函数的返回结果 | 提前 return |
| 值为空时使用备用值 | cmp.Or(value, fallback) |
| 三种或更多情况 | switch |
| 有特定含义、反复出现的条件 | 一个有名字的小函数 |
在 Go 的 issue 跟踪器上,添加条件表达式的提案时常出现,但到目前为止都被拒绝了。写 if 就好。
常见问题
Go 有三元运算符吗?
没有。Go 没有 cond ? a : b 这种表达式。Go FAQ 中说,设计者没有加入它,是因为它太常被用来写出难以阅读的表达式。请改用 if/else 语句。
Go 中如何写一行的条件赋值?
先赋默认值,再覆盖它:
label := "odd"
if n%2 == 0 {
label = "even"
}
这就是惯用的替代写法。gofmt 不会把 if 的代码体放在一行,所以它至少要占三行。
Go 中能写一个泛型的三元函数吗?
可以:func If[T any](cond bool, a, b T) T { if cond { return a }; return b }。问题在于 Go 在调用函数之前会先求值所有参数,所以 a 和 b 都会被计算。If(p != nil, p.Name, "") 仍然会对为 nil 的 p 解引用并 panic。真正的三元运算符只会求值其中一边。
Go 中字符串为空时如何设置默认值?
从 Go 1.22 起,cmp.Or(name, "anonymous") 返回第一个不是零值的参数,所以除非 name 是 "",否则它返回 name。它适用于任何可比较类型,而且和任何函数调用一样,会求值所有参数。