go语言中的常量与枚举

go语言中使用关键字const来定义常量. 最近在项目开发中, 在ovirt-go sdk中经常会用到其内置常量, 善用go的常量, 不仅可以增加代码的可读性, 还能省去一些”小麻烦”~

常量的定义

常量可以单个定义

1
2
const HttpStatusOk = 200
const HttpStatusNotFound = 404

也可以批量定义

1
2
3
4
5
const (
VMStatusDown = "down"
VMStatusUp = "up"
VMStatusImageLocked = "image_locked"
)

常量的优势

go在代码编译前, 变量的类型就已经确定, 并不可更改. 但是常量就比较特殊, 常量在代码编译的时候, 类型才会确定下来, 下面来举个例子🌰

  • 变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

var a int16

func main() {
var b int32

a = 1
b = 2

fmt.Println(a + b)
}

执行结果:

1
2
# command-line-arguments
./demo.go:19:16: invalid operation: a + b (mismatched types int16 and int32)

很显然, 代码在编译前, a,b 两个变量的数据类型已经确定, 两个不同的数据类型, 是不予许进行运算操作的.

  • 常量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

const a = 1
var aa = 1

func main() {
fmt.Printf("type: %v\n", reflect.TypeOf(aa))

var b float32
b = 2.1
fmt.Println(a + b)

var c int32
c = 3
fmt.Println(a + c)
}

运行结果:

1
2
3
type: int
3.1
4

如果其中一个数字是常量的情况, 运行结果就大不相同了, 可以看到, 该程序可以正常执行, 而且拿到了正确的结果. 这是因为, 虽然b变量在编译前确定了数据类型, 但是a常量并没有, a常量是在编译时才根据实际情况确定了数据类型, 常量会根据编译时的运算, 自动执行类型转换

上面的例子中, 声明了a常量, 同时声明了aa变量, 赋值均为1, 通过编译执行, 得到aa的数据类型自动判定为int, 而a常量却可以先和float32类型的b运算, 而后又和int32类型的c进行运算. 说明a常量的数据类型, 是在编译时, 根据实际的运算需要而变化的

所以, 项目中的一些数字可以多多定义为常量, 这样在运算的时候, 也可以减少显式的数据类型转换

枚举

go 语言中没有专门的枚举关键字, 是通过常量const关键字实现的, 枚举的标志性关键字为iota.

iota关键字在const关键字出现的时候, 被重置为0, 且const中每新增一行常量的声明, iota计数器将被+1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

const (
a = iota
b
c
d
)

func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}

运行结果:

1
2
3
4
0
1
2
3

同行声明的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

const (
a, b = iota, iota
c, d = iota, iota
)

func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}

运行结果:

1
2
3
4
0
0
1
1

注意: iota 仅会在新增的常量声明中才会自增, 声明在同一行的常量, iota的值是相同的

跨行声明的情况

  • 情况1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

const (
a = iota
b = 1024
c
d
)

func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}

运行结果:

1
2
3
4
0
1024
1024
1024

const批量声明的特点, 如果后面的常量没有显式赋值, 则它们的值等于上一个显式赋值的值

  • 情况2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

const (
a = iota
b = 1024
c = iota
d
)

func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}

运行结果:

1
2
3
4
0
1024
2
3

由于iota的特点, 每新增一行const的定义, 计数器就会自增1, 所以即使中间显式赋值了一些常量, 依然不会影响iota的自增

枚举的应用

自定义自增大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

const (
_ = iota // 0
a = iota * 2 // 1 * 2
b // 2 * 2
c // 3 * 2
d // 4 * 2
)

func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}

运行结果:

1
2
3
4
2
4
6
8

数据单位运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

const (
b = 1 << (10 * iota) // 1 << (10 * 0)
kb
mb
gb
tb
pb
)

func main() {
fmt.Println(b)
fmt.Println(kb)
fmt.Println(mb)
fmt.Println(gb)
fmt.Println(tb)
fmt.Println(pb)
}

运行结果:

1
2
3
4
5
6
1
1024
1048576
1073741824
1099511627776
1125899906842624
  • b = 1 << (10 * iota)

第一个常量: iota被初始化为0, 则表达式为1 << (10 * 0) –> 1 << 0, 1 向左位移0位, 还是1

  • kb

第二个常量: 没有显式赋值, 该值应该等于上一行显式赋值的”值”, 由于上一行显式赋值的值为一个表达式, 所以将该表达式完整的继承下来. 且该行为新增常量声明, 所以iota变量自增1, 则赋值表达式实际为: 1 << (10 * 1) –> 1 << 10, 1 向左位移 10 位, 二进制表示为10000000000(2的10次方), 转换为10进制为1024, 所以 kb 经过表达式运算后, 值为: 1024

  • mb

第三个常量: 同理, iota 自增 1 后, 赋值表达式实际为: 1 << (10 * 2) —> 1 << 20, 1 向左位移 20 位, 二进制表示为100000000000000000000(2的20次方), 转换为10进制为1048576

  • 以此类推