常量基础
常量在编译期完成初始化:初始化表达式必须能在编译期间求值,并且已初始化的常量可以作为其他常量初始化表达式的一部分。程序整个生命周期中,常量的值保持不变。
常量使用 const 关键字声明。const 支持单行声明多个常量,也支持使用代码块聚合声明。声明常量时可以省略类型,由编译器根据初始值推断(更准确地说,很多情况下会得到“无类型常量”,见后文)。
const PI float64 = 3.14159265358979323846 // 单行常量声明
// 使用代码块形式声明常量
const (
size int64 = 4096 // 显式指定类型
i, j, s = 13, 14, "bar" // 单行声明多个常量,并省略类型
)
常量的取值范围仅限于基本类型及其可表示的值(包括有类型常量与无类型常量):
- 数值常量(整数、浮点数、复数)
- 字符串常量
- 布尔常量
- rune 常量(本质上是整数常量)
常量的命名建议采用驼峰命名法:导出的常量首字母大写,未导出的常量首字母小写。应避免使用其他编程语言常见的命名约定来命名 Go 常量,以下是反例:
const MAX_PACKET_SIZE = 512
const kMaxBufferSize = 1024
const KMaxUsersPerGroup = 500
对于操作系统级别、跨编程语言具有共识或既定含义的常量,允许保留其原始名称。这些名字通常源于操作系统 API 手册或相关语言/平台的标准库文档。
Go 常量的特性
有类型常量与类型一致性
在 Go 中,即便两个类型拥有相同的底层表示,只要是不同的定义类型(defined type),它们之间也不能直接混合运算;这一规则对常量同样适用:
package main
import "fmt"
type myInt int
const n myInt = 13
func main() {
var a int = 5
fmt.Println(a + n) // 编译错误:invalid operation: a + n (mismatched types int and myInt)
}
有类型常量与变量混合运算时,需要显式类型转换:
package main
import "fmt"
type myInt int
const n myInt = 13
const m int = int(n) + 5 // 正确:显式转换
func main() {
var a int = 5
fmt.Println(a + int(n)) // 输出:18
_ = m
}
无类型常量(untyped constant)
为了让常量更方便地参与运算,Go 引入了无类型常量。无类型常量在声明时没有固定类型,只有“种类”(如无类型整数、无类型浮点数、无类型字符串等)。它们在需要具体类型的上下文中,会被转换为目标类型(前提是该值能被目标类型表示)。
例如:
package main
import "fmt"
func main() {
const n = 13 // 无类型整数常量
var a int = 5
fmt.Println(a + n) // n 在此处被转换为 int
}
常量的隐式转换与溢出检查
编译器会根据上下文类型信息,将无类型常量转换为相应类型后参与计算。由于对象是常量,编译器会在编译期进行精确的可表示性检查,确保转换安全;如果目标类型无法表示该常量值,就会报错:
package main
func main() {
const m = 1333333333
var k int8 = 1
_ = k + m // 编译错误:constant 1333333333 overflows int8
}
因此,在很多表达式中不需要显式类型转换,使用无类型常量是一种常见且推荐的写法。
枚举:const + iota
Go 原生没有专门的枚举类型,通常用 const 声明块配合 iota 来实现枚举。与一些语言不同,Go 不会为未赋值的常量自动递增赋值;但它支持“隐式重复前一个非空初始化表达式”和 iota。
隐式重复前一个表达式示例:
const (
Apple, Banana = 11, 22
Strawberry, Grape
Pear, Watermelon
)
等价于:
const (
Apple, Banana = 11, 22
Strawberry, Grape = 11, 22
Pear, Watermelon = 11, 22
)
iota 是预定义标识符,表示在同一个 const 声明块中每一行(更准确地说:每个常量规范项所在行)的序号,从 0 开始递增。iota 本身也是无类型常量,可参与不同类型的求值过程。
例如 Go 标准库中 sync/mutex.go 的一段常量定义:
// $GOROOT/src/sync/mutex.go
const (
mutexLocked = 1 << iota // iota = 0, 1 << 0 = 1
mutexWoken // iota = 1, 1 << 1 = 2
mutexStarving // iota = 2, 1 << 2 = 4
mutexWaiterShift = iota // iota = 3
starvationThresholdNs = 1e6 // iota = 4
)
如果希望枚举从 iota = 1 开始,可以使用空白标识符跳过第一个值:
// $GOROOT/src/syscall/net_js.go
const (
_ = iota
IPV6_V6ONLY // 1
SOMAXCONN // 2
SO_ERROR // 3
)
当需要略过某些值时,也可以利用空白标识符:
const (
_ = iota // 0
Pin1 // 1
Pin2 // 2
Pin3 // 3
_ // 4
Pin5 // 5
)
每个 iota 的生命周期始于一个 const 声明块的开始,并在其结束时终止。
对于不规则序列的常量值,手动指定往往更合适。此外,iota 在一定程度上会影响代码的直观性与可读性,应当在团队约定下谨慎使用。