Go 是一门十分精简的语言。对于每一个引入的语言特性,Go 核心团队都慎之又慎,有时候甚至让人觉得有点死脑筋。
三目表达式条件表达式
什么是三目运算符?
什么是三目运算符
首先,看一下不支持三目表达式特性的 Go 语言的写法
var genderDesc string
if gender == 1{
genderDesc = "男"
} else {
genderDesc = "女"
}
fmt.Println(genderDesc)
再看一下支持三木表达式特性的 C++ 语言的写法
std::string genderDesc = (gender == 1 ? "男" : "女");
std::cout << genderDesc << std::endl;
?:
Go为什么不支持三目运算符?
支持三目运算符的编程语言有很多,C/C++、C#、Java、JavaScript、Python、Ruby等。但 Go 为什么不支持这个主流语言普遍都支持的特性呢?从 Go 语言的 FAQ 中可以略知一二。
The reason ?: is absent from Go is that the language's designers had seen the operation used too often to create impenetrably complex expressions. The if-else form, although longer, is unquestionably clearer. A language needs only one conditional control flow construct.
Go 核心团队认为,程序员常常会利用三目运算符构建及其复杂的表达式,而这么复杂的表达式一定都可以通过拆解成一个或者多个 if 语句来实现,并且 if 语句的可读性更好。顺便猜测一下,因为 Go 核心团队成员都是拥有多年经验的 C/C++ 大师,对于 C++ 那不断膨胀的语言特性一定也是心有余悸。他们不想让 Go 走上 C++ 的老路,不想在 Go 语言中出现做同一件事却有10种炫技式的不同写法的现象,他们希望在 Go 中有且只有一种写法。
对于官方给出的这么官方的回答,肯定有人同意,也有人会反对。显然我属于后者,否则也不会有这篇吐槽文章。至于原因,请继续往下看。
三目运算符有哪些常见的使用场景?
同一个语言特性,不同的人会有不同的使用方式。我无法穷举三目运算符的所有使用方式,下面我按照是否嵌套的的三目运算符划分了两个常见的场景。
- 场景一:无三目运算符嵌套
#include <iostream>
struct Person {
unsigned char gender;
};
int main() {
Person p;
std::cout << (p.gender == 1 ? "男" : "女") << std::endl;
}
可见,在没有三目运算符嵌套的情况下,并不会对代码可读性产生任何影响。当然,我也见过极个别网友说这种代码的可读性也不高。对于这种例外,我只能建议其去看眼科了。
- 场景二:三目运算符嵌套
#include <iostream>
struct Person {
unsigned char gender;
};
int main() {
Person *p = new Person;
p->gender = 1;
std::cout << (p == nullptr ? "未知" : (p->gender == 1 ? "男" : "女")) << std::endl;
delete p;
}
对于三目运算符嵌套的场景,最常见的也是示例中的两层嵌套,多于两层的嵌套就会对代码可读性产生较大影响。但我认为,这不能成为否定三目运算符积极意义的理由,if 的多层嵌套同样会影响代码可读性。下面再看一下没有三目运算符特性的 Go 语言的写法。
package main
import "fmt"
type Person struct {
gender uint8
}
func main() {
var genderDesc string
p := new(Person)
if p == nil {
genderDesc = "未知"
} else {
if p.gender == 1 {
genderDesc = "男"
} else {
genderDesc = "女"
}
}
fmt.Println(genderDesc)
}
代码可读性
genderDesc
如何造一个三目运算符的轮子?
既然 Go 不支持三目运算符,而我又需要它,那只能尝试去造一个这样的轮子。我会使用三种方式来造这个轮子,代码也没几行,实现起来也不难,那为什么还要官方作为语言特性提供呢?那些认为三目运算符没必要的人可能也会拿这个当作理由。看完下面再来评价这个轮子好不好。
- 版本一:为每种基本数据类型添加一个类似三目运算符的条件表达式函数
package condexpr
// Str string类型的条件表达式函数
func Str(expr bool, a, b string) string {
if expr {
return a
}
return b
}
// Int int类型的条件表达式函数
func Int(expr bool, a, b int) int {
if expr {
return a
}
return b
}
// Float64 float64类型的条件表达式函数
func Float64(expr bool, a, b float64) float64 {
if expr {
return a
}
return b
}
genderDesc := condexpr.Str(p.gender == 1, "男", "女")
interface{}
package condexpr
// Interface interface{}类型的条件表达式函数
func Interface(expr bool, a, b interface{}) interface{} {
if expr {
return a
}
return b
}
genderDesc := condexpr.Interface(p.gender == 1, "男", "女").(string)interface{}
- 版本三:使用泛型特性定义一个万能条件表达式函数
package condexpr
// Any 若expr成立,则返回a;否则返回b。
func Any[T any](expr bool, a, b T) T {
if expr {
return a
}
return b
}
genderDesc := condexpr.Any(p.gender == 1, "男", "女")
var p *Person
genderDesc := Any(p == nil, "未知", Any(p.gender == 1, "男", "女")) // panic
fmt.Println(genderDesc)
p == nilp.gender
p == nilp.gender == 1
总结
我个人极为期待三目运算符这个语言特性,因为它能让代码更加简洁,它能减少非必要变量的定义,降低程序员在命名方面的负担。虽然,可以人为地通过函数方式模拟三目运算符,但这种方式依然做不到语言特性级别的惰性计算,无法真正取代三目运算符。
参考
本文使用 Zhihu On VSCode 创作并发布