Menu

Golang 三元运算符:Go 为什么没有,该用什么代替

Go 没有三元运算符(cond ? a : b)。本页解释原因,展示惯用的 if/else 替代写法、用 cmp.Or 设置默认值,以及泛型辅助函数和它与真正三元运算符不同的陷阱。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

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。它适用于任何可比较类型,而且和任何函数调用一样,会求值所有参数。

Coddy 编程语言插图

和 Coddy 一起学编程

开始