无论你是学习 C/C++ 还是 Java,肯定遇到过常量这种概念。Go 语言当然也有常量,在前面的文章里,你肯定遇见过很多次了。下面就是在 Go 里定义的一个常量。
const pi = 3.1415926
不过很奇怪的是,为什么定义常量的时候没有指定类型?它也是自动推导的吗?这个问题待会我们再说清楚。
1. 一些特性
- Go 语言的常量是在编译期进行计算的
- 常量之间所有的运算,结果仍然是常量。这个特性可以提升程序的运行效率,将运行期的计算转移到编译期完成,是一种常见的编程技法。
举个例子:
const a = 2
const b = 2 * a // b 在编译期完成计算
2. const 关键字
正如上述所见,Go 中使用 const 关键字声明常量。
- 例 1
package main
import "fmt"
const (
pi = 3.141592653589793238462643383279
e = 2.718281828459045235360287471352
i = 100
ii = 2 * i
c = 4 + 0i
r = 'a'
rr = '中'
s = "中国"
b = true
)
func main() {
fmt.Printf("%T: %[1]v\n", pi) // float64: 3.141592653589793
fmt.Printf("%T: %[1]v\n", e) // float64: 2.718281828459045
fmt.Printf("%T: %[1]v\n", i) // int: 100
fmt.Printf("%T: %[1]v\n", ii) // int: w00
fmt.Printf("%T: %[1]v\n", c) // complex128: (4+0i)
fmt.Printf("%T: %[1]v\n", r) // int32: 97
fmt.Printf("%T: %[1]v\n", rr) // int32: 20013
fmt.Printf("%T: %[1]v\n", s) // string: 中国
fmt.Printf("%T: %[1]v\n", b) // bool: true
var x float64 = i
fmt.Printf("%T: %[1]v\n", x) // float64: 100
fmt.Printf("%T: %[1]v\n", i/6)// int: 16
}
- 例 2
package main
import "fmt"
func main() {
const (
a = 8
b = 2.2 * a
c float64 = 5
)
fmt.Printf("%T %[1]v\n", a) // int 8
fmt.Printf("%T %[1]v\n", b) // float64 17.6
fmt.Printf("%T %[1]v\n", c) // float64 5
}
上面的两个例子里,大多数 const 声明的常量都是没有指定类型的,但是例 2 中,你也可以显式指定类型。没有指定类型的常量,在 Go 里称之为无类型常量(untyped constant),但并不是真的就是没有类型。接下来,我们来说说这种 untyped constant 是啥。
2.1 untyped constant
实际上,没有显式指定 type 的常量,在 Go 里称为 uncommitted constant (未提交、未明确的常量),意思就是说这种常量的类型暂时还不能确认,以后在用到它的时候再确认也不迟。
换言之,那些显式指定了类型的 constant 称为 committed constant.
举例:
const a = 10
fmt.Printf("%T\n", a)
var b float64 = 4 * a // 在需要的时候,a 转变成了 float64
fmt.Printf("%T\n", b)
在 Go 里,uncommitted constant 拥有比普通的 int/float64
更高的运算精度(gopl 说可以认为其精度达到 256bit)。
前面我们也说了,untyped constant 并不是说它没有类型,实际上在第 1 节的两个例子中,我们看到了 untyped constant 可以通过 Printf
函数打印出其类型,这是因为在 constant 在此处隐式转换成了对应基础类型的常量。
这些隐式转换的规则是什么?
Go 里,有 6 种 uncommitted constant,分别是:
- untyped boolean
- untyped integer (隐式转换成 int)
- untyped rune (隐式转换成 int32)
- untyped floaing-point (隐式转换成 float64)
- untyped complex (隐匿转换成 complex128)
- untyped string
uncommitted constant 另一个特点是在做类型转换时,不需要显式的进行强制转换,前面也说了,它会在需要的时候进行类型转换。
3. iota
在 Go 里,iota 被称之为 constant generator (常量生成器)。你可以把 iota 理解成一个函数(并不真的是函数,它是由编译器自动处理的),这个函数每使用一次,返回的值就自动 + 1.
- 例 1
package main
import "fmt"
const (
a = iota
b = iota + 9
c = iota - 4
d = iota
)
func main() {
fmt.Printf("%T: %[1]v\n", a) // int: 0
fmt.Printf("%T: %[1]v\n", b) // int: 10
fmt.Printf("%T: %[1]v\n", c) // int: -2
fmt.Printf("%T: %[1]v\n", d) // int: 3
}
不过在实际使用中,如果后面一行的 iota 表达式和上一行完全一样,则可以省略不写,见例 2.
- 例 2
package main
import "fmt"
const (
Sunday = iota
Monday // = iota
Tuesday
Wednesday
Thursday
Friday
Saturday
)
func main() {
fmt.Printf("%T: %[1]v\n", Sunday) // int: 0
fmt.Printf("%T: %[1]v\n", Monday) // int: 1
fmt.Printf("%T: %[1]v\n", Tuesday) // int: 2
fmt.Printf("%T: %[1]v\n", Wednesday) // int: 3
fmt.Printf("%T: %[1]v\n", Thursday) // int: 4
fmt.Printf("%T: %[1]v\n", Friday) // int: 5
fmt.Printf("%T: %[1]v\n", Saturday) // int: 6
}
这特别像 C++ 语言中的枚举类型,给第一个元素赋值后,后面的元素值自动 + 1. 不过 iota 更加强大,它可以生成更加复杂的值:
- 例3
package main
import "fmt"
const (
a = 1 << iota
b // = 1 << iota,后面都不用写了,都一样
c
d
)
func main() {
fmt.Printf("%T: %[1]v\n", a) // int: 1
fmt.Printf("%T: %[1]v\n", b) // int: 2
fmt.Printf("%T: %[1]v\n", c) // int: 4
fmt.Printf("%T: %[1]v\n", d) // int: 8
}
- 例 4
package main
import "fmt"
const (
_ = 1 << (10 * iota)
KiB
MiB
GiB
TiB
PiB
EiB
ZiB
YiB
)
func main() {
fmt.Printf("%T: %[1]v\n", KiB) // int: 1024
fmt.Printf("%T: %[1]v\n", MiB) // int: 1048576
fmt.Printf("%T: %[1]v\n", GiB) // int: 1073741824
fmt.Printf("%T: %[1]v\n", TiB) // int: 1099511627776
fmt.Printf("%T: %[1]v\n", PiB) // int: 1125899906842624
fmt.Printf("%T: %[1]v\n", EiB) // int: 1152921504606846976
// 还记得吗?常量之间的运算结果仍然是常量。YiB/ZiB 是编译期计算出来的。
fmt.Printf("%T: %[1]v\n", YiB/ZiB) // int: 1024
// 下面这两行不注释掉,编译会报错。
// fmt.Printf("%T: %[1]v\n", ZiB)
// fmt.Printf("%T: %[1]v\n", YiB)
}
4. 总结
- 掌握 const 关键字
- 掌握 iota