Go语言精进之路:从新手到高手的编程思想、方法和技巧(1)
上QQ阅读APP看书,第一时间看更新

9.1 Go常量溯源

先来回顾一下C语言。在C语言中,字面值(literal)担负着常量的角色(针对整型值,还可以使用枚举常量)。可以使用整型、浮点型、字符串型、字符型字面值来满足不同场合下对常量的需求:

0x12345678
10086
3.1415926
"Hello, Gopher"
'a'

为了不让这些魔数(magic number)充斥于源码各处,早期C语言的常用实践是使用宏(macro)定义记号来指代这些字面值:

#define MAX_LEN 0x12345678
#define CMCC_SERVICE_PHONE_NUMBER 10086
#define PI 3.1415926
#define WELCOME_TO_GO "Hello, Gopher"
#define A_CHAR 'a'

这种定义“具名字面值”的实践也被称为宏定义常量。虽然后续的C标准中提供了const关键字来定义在程序运行过程中不可改变的变量(又称“只读变量”),但使用宏定义常量的习惯依然被沿袭下来,并且依旧是C编码中的主流风格。

宏定义的常量有着诸多不足,比如:

  • 仅是预编译阶段进行替换的字面值,继承了宏替换的复杂性和易错性;
  • 是类型不安全的;
  • 无法在调试时通过宏名字输出常量的值。

而C语言中const修饰的标识符本质上还是变量,和其他变量一样,编译器不能像对待真正的常量那样对其进行代码优化,也无法将其作为数组声明时的初始长度。

Go语言是站在C语言等编程语言的肩膀之上诞生的,它原生提供常量定义的关键字const。Go语言中的const整合了C语言中宏定义常量、const只读变量和枚举常量三种形式,并消除了每种形式的不足,使得Go常量成为类型安全且对编译器优化友好的语法元素。Go中所有与常量有关的声明都通过const来进行,例如:

// $GOROOT/src/os/file.go
const (
    O_RDONLY int = syscall.O_RDONLY
    O_WRONLY int = syscall.O_WRONLY
    O_RDWR   int = syscall.O_RDWR
    O_APPEND int = syscall.O_APPEND
    ...
)

上面这段标准库中的代码通过const声明了一组常量,如果非要进一步细分,可以将这组常量视为枚举的整型常量。然而你可能没想到,上面对常量的声明方式仅仅是Go标准库中的少数个例,绝大多数情况下,Go常量在声明时并不显式指定类型,也就是说使用的是无类型常量(untyped constant)。比如:

// $GOROOT/src/io/io.go
const (
    SeekStart   = 0
    SeekCurrent = 1
    SeekEnd     = 2
)

无类型常量是Go语言在语法设计方面的一个“微创新”,也是“追求简单”设计哲学的又一体现,它可以让你的Go代码更加简洁。接下来我们就来看看无类型常量是如何简化Go代码编写的。