常量表示运行时恒定不可改变的值,通常是一些字面量。使用常量就可用一个易于阅读理解的标识符号来代替“魔法数字”,也使得在调整常量值时,无须修改所有引用代码。
常量值必须是编译期可确定的字符、字符串、数字或布尔值。可指定常量类型,或由编译器通过初始化值推断,不支持 C/C++ 数字类型后缀。
const x, y int = 123, 0x22
const s = "hello,world!"
const c = '我' // rune(unicode code point)
const (
i, f = 1, 0.123 // int,float64(默认)
b = false
)
可在函数代码块中定义常量,不曾使用的常量不会引发编译错误。
func main() {
const y = 1.23 // 未使用,不会引发编译错误
{
const x = "abc" // 在不同作用域定义同名常量
println(x)
}
}
如果显式指定类型,必须确保常量左右值类型一致,需要时可做显式转换。右值不能超出常量类型取值范围,否则会引发溢出错误。
const (
x, y int = 99, -999
b byte = byte(x) // x被指定为int类型,须显式转换为byte类型
n = uint8(y) // 错误: constant-999 overflows uint8
)
常量值也可以是某些编译器能计算出结果的表达式,如 unsafe. Sizeof、len、cap 等。
import "unsafe"
const (
ptrSize = unsafe.Sizeof(uintptr(0))
strSize = len("hello,world!")
)
在常量组中如不指定类型和初始化值,则与上一行非空常量右值(表达式文本)相同。
const (
x uint16 = 120
y // 与上一行x类型、右值相同
s = "abc"
z // 与s类型、右值相同
)
变量有“零值”,常量没有。
// const declaration cannot have type without expression
// missing value in const declaration
const (
a int
)
const b bool
枚举
Go 并没有明确意义上的 enum 定义,不过可借助 iota 标识符实现一组自增常量值来实现枚举类型。
const (
x = iota // 0
y // 1
z // 2
)
const(
_ = iota // 0
KB = 1<< (10*iota) // 1 << (10*1)
MB // 1 << (10*2)
GB // 1 << (10*3)
)
自增作用范围为常量组。可在多常量定义中使用多个 iota,它们各自单独计数,只须确保组中每行常量的列数量相同即可。
const(
_, _ = iota, iota*10 // 0, 0*10
a, b // 1, 1*10
c, d // 2, 2*10
)
如中断 iota 自增,则必须显式恢复。且后续自增值按行序递增,而非 C enum 那般按上一取值递增。
const(
a = iota // 0
b // 1
c = 100 // 100
d // 100(与上一行常量右值表达式相同)
e = iota // 4(恢复iota自增,计数包括c、d)
f // 5
)
自增默认数据类型为 int,可显式指定类型。
const (
a = iota // int
b float32 = iota // float32
c = iota // float32(如不显式指定iota,则与b数据类型相同)
)
在实际编码中,建议用自定义类型实现用途明确的枚举类型。但这并不能将取值范围限定在预定义的枚举值内。
type color byte // 自定义类型
const (
black color = iota // 指定常量类型
red
blue
)
func test(c color) {
println(c)
}
func main() {
test(red)
test(100) // 100并未超出color/byte类型取值范围
x := 2
test(x) // 错误: cannot use x(type int)as type color in argument to test
}
展开
常量除“只读”外,和变量究竟有什么不同?
var x = 0x100
const y = 0x200
func main() {
println(&x, x)
println(&y, y) // 错误: cannot take the address of y
}
不同于变量在运行期分配存储内存(非优化状态),常量通常会被编译器在预处理阶段直接展开,作为指令数据使用。
数字常量不会分配存储空间,无须像变量那样通过内存寻址来取值,因此无法获取地址。
提到常量展开,我们还须回头看看常量的两种状态对编译器的影响。
const x = 100 // 无类型声明的常量
const y byte = x // 直接展开x,相当于const y byte = 100
const a int = 100 // 显式指定常量类型,编译器会做强类型检查
const b byte = a // 错误: cannot use a(type int)as type byte in const initializer