Menu

Golang const 常量:有类型与无类型常量详解

Go 中 const 的工作方式:声明常量、有类型常量与无类型常量的区别、任意精度的常量表达式,以及为什么 Go 没有常量切片、map 或结构体。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

常量是在编译期就确定的值。用 const 声明它,Go 会拒绝任何修改它的尝试。

常量可以在包级别或函数内部声明,可以每行一个,也可以放在 const ( ... ) 块中。MB 和 GB 由其他常量计算得到,这些运算由编译器在构建时完成一次。

给常量赋值(例如 MaxRetries = 5)是编译错误:

./main.go:8:2: cannot assign to MaxRetries (neither addressable nor a map index expression)

哪些值可以是常量

只有三类值:布尔值、数字(整数、浮点数、复数和 rune)以及字符串。值必须能由编译器算出,也就是字面量、其他常量、对它们的算术运算,以及少数内置函数,比如对常量字符串取 len。

任何需要运行代码或分配内存的东西都不行:

const Colors = []string{"red", "green"} // slice
const Started = time.Now()              // function call
./main.go:9:16: []string{…} (value of type []string) is not constant
./main.go:10:13: time.Now() (value of struct type time.Time) is not constant

为什么 Go 没有常量切片或 map

切片、map 以及包含它们的结构体都是对运行时内存的引用,所以编译器无法把它们作为固定值写进程序,而 Go 也没有用于变量的 readonly 或 final 修饰符。常见的变通办法:

每次调用都会产生一次内存分配,对于配置这种规模的数据来说没有问题。如果要按键查找,函数中的 switch 是另一种类似常量的选择:func statusText(code int) string { switch code { ... } }。

无类型常量

声明时没有指定类型的常量是无类型的。它有一个种类(整数、浮点数、rune、字符串、布尔值),但在被使用之前没有具体的 Go 类型,会根据上下文自动适配:

Ratio 不需要转换就能当作 int、float64、uint8 和 float32 使用。这就是 time.Sleep(2 * time.Second) 能通过编译的原因:2 是无类型的,会变成 time.Duration。

当上下文不要求特定类型时,例如 x := Ratio,常量会使用它的默认类型:

无类型常量的种类默认类型
整数(42)int
浮点数(4.2)float64
rune('a')rune(int32)
复数(2i)complex128
字符串string
布尔值bool

精确运算

无类型数值常量是精确的。编译器至少用 256 位来表示整数,所以中间值可以远远超出任何 Go 类型的范围:

Huge 本身不能作为整数打印,因为把它传给 fmt.Println 会把它转换成 int,编译器会发现这个问题(float64(Huge) 可以,因为浮点数能容纳这么大的数量级):

cannot use Huge (untyped int constant 1267650600228229401496703205376) as int value in argument to fmt.Println (overflows)

同样的检查也能在编译期拦住更小的错误,比如 var b byte = 300:

cannot use 300 (untyped int constant) as byte value in variable declaration (overflows)

有类型常量

给常量指定类型后,它在任何地方的行为都和该类型的值一样,包括 Go 不允许混用类型这一点:

const Limit int = 10

var f float64 = 2
fmt.Println(Limit * f)
invalid operation: Limit * f (mismatched types int and float64)

如果写成 const Limit = 10(无类型),Limit * f 就能通过编译,结果是 20。所以除非类型本身有意义,否则让常量保持无类型。当你声明属于自定义命名类型的常量时,类型就有意义了,Go 正是这样实现枚举的:

type Weekday int

const (
	Sunday Weekday = iota
	Monday
	Tuesday
)

这里类型才是重点:接受 Weekday 的函数清楚地说明了它需要什么。iota 以及基于它的枚举写法会在枚举和 iota 页面中讲解。

const 与 var

constvar
能否改变不能能
值何时确定编译期运行时
允许的类型布尔、数值、字符串任意类型
运行时占用内存否是
能否取地址(&x)不能能
未使用时是否报错否是(局部变量)

真正固定不变的东西都用 const:上限、大小、名称、协议代码、格式字符串。你会免费获得编译期溢出检查和精确运算。

易错点

有类型常量不会自动适配。 const Timeout int = 5 不经转换就不能传给需要 time.Duration 的地方;无类型的 const Timeout = 5 则可以直接相乘:Timeout * time.Second。

整数常量做的是整数除法。 const Half = 1 / 2 的结果是 0,因为两个操作数都是无类型整数。写成 1.0 / 2 才能得到 0.5。

不能取地址。 &MaxRetries 无法通过编译。如果某个 API 需要 *int,先把常量复制到一个变量中。

常见问题

Go 中如何声明常量?

使用 const:const MaxRetries = 3,或者显式指定类型:const Timeout time.Duration = 5 * time.Second。多个常量可以放在一个块中:const ( A = 1; B = 2 )。值必须在编译期就能算出来。

Go 中可以定义常量切片、map 或数组吗?

不可以。常量只能是布尔值、数字(包括 rune)和字符串。const Colors = []string{"red"} 会报错 is not constant。请改用包级别的 var;如果调用方不能修改它,就提供一个返回新副本的函数。

Go 中的无类型常量是什么?

声明时没有指定类型的常量,例如 const Pi = 3.14159。在被使用之前它没有固定的类型,所以同一个常量可以当作 float32、float64 使用,在值允许的情况下也可以当作 int。无类型常量还是精确的:编译器至少保留 256 位精度,所以只要用到它的表达式结果放得下,const Big = 1 << 100 就是合法的。

Go 中 const 和 var 有什么区别?

const 在编译期就已确定,永远不能改变;它在运行时不占内存,只能保存布尔值、数字或字符串。var 是一块可以重新赋值的内存,可以保存任何类型,包括运行时才算出来的值,例如 time.Now()。

Coddy 编程语言插图

和 Coddy 一起学编程

开始