- Golang 官方文档: golang.google.cn
- C 语言中文网: Golang 简介
常量和 const 关键字
常量声明
go 语言中的常量使用关键字 const
定义,用于存储不会改变的数据,常量是在编译时被创建的,即使定义在函数内部也是如此,并且只能是布尔型,数字型(整数型、浮点型和复数)和字符串型。由于编译时的限制,定义常量的表达式必须为能被编译器求值的常量表达式。
常量的定义格式和变量的声明语法类似,例如:
1
2
3
4const name [type] = value
// 例如
const pi = 3.14159 // 相当于 math.Pi 的近似值在 go 语言中,可以省略类型说明符
[type]
,因为编译器可以根据变量的值来推导其类型- 显式类型定义:const b string = “abc”
- 隐式类型定义:const b = “abc”
常量的值必须是能够在编译时就能够确定的,可以在其赋值表达式中涉及计算过程,但是所有用于计算的值必须在编译期间就能获得,比如:
- 正确的做法: const c1 = 2/3
- 错误的做法: const c2 = getNumber() // 引发构建错误,
getNumber()
用作值
和变量声明一样,也可以批量声明多个常量:
1
2
3
4const (
e = 2.7182818
pi = 3.1415926
)所有常量的运算都可以在编译期完成,这样不仅可以减少运行时的工作,也方便其他代码的编译优化,当操作数是常量时,一些运行时的错误也可以再编译时被发现,例如整数除零,字符串索引越界,任何导致无效浮点数的操作等。
常量间的所有算数运算,逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都是返回常量结果
1
len、cap、real、imag、complex 和 unsafe.Sizeof
因为他们的值是在编译期就确定的,因此常量可以是构成类型的一部分,例如用于指定数组类型的长度:
1
2
3
4
5
6
7const IPv4Len = 4
//parseIPv4 解析一个 IPv4 地址(d.d.d.d).
func parseIPv4(s string) IP {
var p [IPv4Len]byte
// ...
}一个常量的声明也可以包含一个类型和一个值,但是如果没有显式指明类型,那么将从右边的表达式推断类型。在下面的代码中,
time.Duration
是一个命名类型,底层类型是 int64,time.Minute 是对应类型的常量。下面声明的两个常量都是 time.Duration 类型,可以通过%T
参数打印类型信息1
2
3
4
5
6const noDelay time.Duration = 0
const timeout = 5 * time.Minute
fmt.Printf("%T %[1]v\n", noDelay) // "time.Duration 0"
fmt.Printf("%T %[1]v\n", timeout) // "time.Duration 5m0s"
fmt.Printf("%T %[1]v\n", time.Minute) // "time.Duration 1m0s"如果是批量声明的常量,除第一个外,其他的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式,对应的常量类型也是一样的。例如:
1
2
3
4
5
6
7
8const (
a = 1
b
c = 2
d
)
fmt.Println(a, b, c, d) // "1 1 2 2"如果只是简单地复制右边的常量表达式,其实并没有太实用的价值。但是它可以带来其它的特性,那就是 iota 常量生成器语法。
iota 常量生成器
常量声明可以使用 iota 常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。在一个 const 声明语句中,在第一个声明的常量所在的行,iota 将被被置为 0,然后在每一个有常量声明的行加一。
示例:首先定义一个 Weekday 命名类型,然后为一周的每天定义了一个常量,从周日 0 开始。在其他编程语言中,这种类型一般被称为枚举类型
1
2
3
4
5
6
7
8
9
10
11type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Staturday
)周日将对应 0,周一为 1,以此类推。
无类型常量
go 语言有个不同寻常之处。虽然一个常量可以有任意一个确定的基础类型,例如 int 或者 float64,或者是类似 time.Duration 这样的基础类型,但是许多常量并没有一个明确的基础类型。
编译器为这些没有明确的基础类型的数字常量提供比基础类型更高精度的算数运算,可以认为至少有 256bit 的运算精度。这里有六种未明确类型的常量类型,分别是:
- 无类型的布尔型
- 无类型的整数
- 无类型的字符
- 无类型的浮点数
- 无类型的复数
- 无类型的字符串
通过延迟明确常量的具体类型,不仅可以提供更高的运算精度,而且可以直接用于更多的表达式而不需要显式的类型转换。
示例: math.Pi 无类型的浮点数常量,可以直接用于任意需要浮点数或复数的地方
1
2
3var x float32 = math.Pi
var y float64 = math.Pi
var z complex128 = math.Pi如果 math.Pi 被确定为特定类型,比如 float64,那么结果精度可能会不一样,同时对于需要 float32 或 complex128 类型值的地方则需要一个明确的强制类型转换:
1
2
3
4
5const Pi64 float64 = math.Pi
var x float32 = float32(Pi64)
var y float64 = Pi64
var z complex128 = complex128(Pi64)
对于常量面值,不同的写法可能会对应不同的类型。例如 0
、0.0
、0i
和 \u0000
虽然有着相同的常量值,但是它们分别对应无类型的整数、无类型的浮点数、无类型的复数和无类型的字符等不同的常量类型。同样,true
和 false
也是无类型的布尔类型,字符串面值常量是无类型的字符串类型。
go 语言模拟枚举
Go 语言现阶段没有枚举类型,但是可以使用 const 常量配合 iota 来模拟枚举类型,请看下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16type Weapon int
const (
Arrow Weapon = iota
Shuriken
SniperRife
Rife
Blower
)
// 输出所有枚举值
fmt.Println(Arrow, Shuriken, SniperRifle, Rifle, Blower)
// 使用枚举类型并赋初值
var weapon Weapon = Blower
fmt.Println(weapon)代码输出如下:
1
20 1 2 3 4
4代码说明:
- 第 1 行中,将 int 定义为 Weapon 类型,就像枚举类型的本质是一个 int 类型一样。当然,某些情况下,如果需要 int32 和 int64 的枚举,也是可以的。
- 第 4 行中,将常量 Arrow 的类型标识为 Weapon,这样标识后,const 下方的常量可以使用 Weapon 作为默认类型。该行使用 iota 进行常量值自动生成,iota 的起始值为 0,一般情况下也是建议枚举从 0 开始,让每个枚举类型都有一个空值,方便业务和逻辑的灵活使用。
- 一个 const 声明内的每一行常量声明,将会自动套用前面的 iota 格式,并自动增加,类似于电子表格中单元格自动填充的效果,只需要建立好单元格之间的变化关系,拖动右下方的小点就可以自动生成单元格的值。
当然,iota 不仅可以生成每次增加 1 的枚举值。还可以利用 iota 来做一些强大的枚举常量值生成器。下面的代码可以方便的生成标志位常量:
1
2
3
4
5
6
7
8
9const (
FlagNone = 1 << iota
FlagRed
FlagGreen
FlagBlue
)
fmt.Printf("%d %d %d\n", FlagRed, FlagGreen, FlagBlue)
fmt.Printf("%b %b %b\n", FlagRed, FlagGreen, FlagBlue)代码输出如下:
1
22 4 8
10 100 1000在代码中编写一些标志位时,我们往往手动编写常量值,常量值特别多时,很容易重复或者写错,因此,使用 ioto 自动生成更加方便。
代码说明如下:
- 第 2 行中 iota 使用了一个移位操作,每次将上一次的值左移一位(二进制位),以得出每一位的常量值。
- 第 8 行,将 3 个枚举按照常量输出,分别输出 2、4、8,都是将 1 每次左移一位的结果。
- 第 9 行,将枚举值按二进制格式输出,可以清晰地看到每一位的变化。
将枚举值转换为字符串
枚举在 C# 中是一个独立的类型,可以通过枚举值获取该值对应的字符串。例如,C# 中 Week 枚举值 Monday 为 1,那么可以通过 Week.Monday.ToString()
函数获得 Monday 字符串。
Go语言中也可以实现这一功能,代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30package main
import "fmt"
// 声明芯片类型
type ChipType int
const (
None ChipType = iota
CPU // 中央处理器
GPU // 图形处理器
)
func (c ChipType) string() string {
switch c {
case None:
return "None"
case CPU:
return "CPU"
case GPU:
return "GPU"
}
return "N/A"
}
func main() {
// 输出 CPU 的值并以整型格式显示
fmt.Printf("%s %d", CPU, CPU)
}运行结果如下
1
CPU 1
代码说明如下:
- 第 6 行,将 int 声明为 ChipType 芯片类型。
- 第 9 行,将 const 里定义的常量值设为 ChipType 类型,且从 0 开始,每行值加 1。
- 第 14 行,定义 ChipType 类型的方法 String(),返回值为字符串类型。
- 第 15~22 行,使用 switch 语句判断当前的 ChitType 类型的值,返回对应的字符串。
- 第 30 行,按整型的格式输出 CPU 的值。
String() 方法的 ChipType 在使用上和普通的常量没有区别。当这个类型需要显示为字符串时,Go语言会自动寻找 String() 方法并进行调用。