常量基础
常量的初始化:在编译期间完成初始化,初始化表达式必须能够在编译期间求出结果,同时已经被初始化的常量可以作为其它常量初始化表达式的一部分。在程序的整个生命周期中,常量的值保持不变,
常量的声明方式与变量不同,使用const关键字声明常量,const支持单行声明多个常量,以及代码块形式聚合声明。声明常量时也可以省略类型,由编译器自动判断类型。
const PI float64 = 3.14159265358979323846 // 单行常量声明
// 使用代码块形式声明常量
const (
size int64 = 4096 // 显式地指定类型
i, j, s = 13, 14, "bar" // 单行声明多个常量,并且省略类型
)
常量的类型仅限于基本数据类型:
- 数值类型
- 字符串类型
- 布尔类型
常量的名称应采用驼峰命名法,导出的常量首字母大写,未导出的常量首字母小写,应避免使用其它变成语言的命名约定来命名Go常量,以下是反例:
const MAX_PACKET_SIZE = 512
const kMaxBufferSize = 1024
const KMaxUsersPergroup = 500
对于操作系统级别、具有夸编程语言共识或标注的常量,允许保留最初的名字,这些名字通常源于各种操作系统API手册及变成语言的标准库参考手册。
Go常量的创新
无类型常量
在Go中,即便两个类型拥有相同的存储结构,但在声明时使用了不同的类型,这两个类型也不能同时进行运算,这一规则在常量中同样适用:
type myInt int
const n myInt = 13
const m int = n + 5 // 编译器错误:cannot use n + 5 (type myInt) as type int in const initializer
func main() {
var a int = 5
fmt.Println(a + n) // 编译器错误:invalid operation: a + n (mismatched types int and myInt)
}
在有类型常量与变量混合运算时,只能通过显式类型转换:
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
}
为了让常量可以方便地参与运算,Go中使用无类型常量解决这一问题。和变量一样,无类型常量在初始化时,根据初始值决定一个默认类型,并在参与运算时,自动进行隐式转型,转换为对应的类型后参与运算。
隐式转型
编译器会根据上下文中的类型信息,自动将无类型常量转换为相应的类型后参与计算,由于转型的对象是常量,因此不会引发类型安全问题,编译器可以确保转型安全。
如果编译器在尝试进行隐式转型时发现无法将常亮转换为目标类型,也会产生报错:
const m = 1333333333
var k int8 = 1
j := k + m // 编译器错误:constant 1333333333 overflows int8
有了无类型常量和隐式转型,我们在很多情况下,不需要在表达式中进行显式类型转换,使用无类型常量是一种惯用法。
枚举
Go原生没有提供枚举类型,我们可以使用const代码块定义的常量集合实现枚举。和其他语言中的枚举不同,如果常量没有显式复制,Go不会自动为其指定值,但引入了“隐式重复前一个非空表达式”和iota关键字。
const (
Apple, Banana = 11, 22
Strawberry, Grape
Pear, Watermelon
)
以上代码定义的常量中,后两行没有显式赋予初始值,Go编译器会自动使用上一行的初始化表达式,等价于:
const (
Apple, Banana = 11, 22
Strawberry, Grape = 11, 22 // 使用上一行的初始化表达式
Pear, Watermelon = 11, 22 // 使用上一行的初始化表达式
)
由于常量的值往往都是唯一的,单纯的重复可能并不能实现我们的需求,Go在此基础上引入了iota关键字。
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, 显式指定初始值为iota
starvationThresholdNs = 1e6 // iota = 4, 显式指定初始值,使用指定的值
)
如果希望枚举常量从iota = 1开始,可以参考Go标准库中的做法,使用空白标识符忽略iota的第一个值:
// $GOROOT/src/syscall/net_js.go
const (
_ = iota
IPV6_V6ONLY // 1
SOMAXCONN // 2
SO_ERROR // 3
)
当需要略过某些值时,也可以利用空白标识符:
const (
_ = iota // 0
Pin1
Pin2
Pin3
_
Pin5 // 5
)
每个iota的生命周期始于一个const声明块的开始,并在其结束时终止。
对于不规则序列的常量值,往往手动指定更为合适。此外,iota一定程度上影响了代码的可读性和直观性,应当谨慎使用。
评论区