参考: 1.5 iota · 《GO专家编程》 (kilvn.com)
iota
常用于枚举值定义场景, 使用也颇为广泛. 本文将简单介绍下iota
的实现原理.
引言
iota
是Go
预定义的标识符, 其会按照递增的顺序自动生成一组相关值, 起始为0
.当 iota
在常量声明中被使用时,它会自动递增,直到遇到一个新的常量声明或 const
关键字。下面是一个简单的例子:
1 | package main |
依次输出0 , 1, 2
;
那么如果一行中出现多个iota
相关的表达式呢? 例如:
1 | package main |
上面的输出内容是什么?
如果
iota
按照声明的个数来自增的话:1
2
3
4
5
6
7// a, a1 = iota(0), 1 << iota(1)
// b, b1 = iota(2), 1 << iota(3)
// c, c1 = iota(4), 1 << iota(5)
a: 0, a1: 2
b: 2, b1: 8
c: 4, c1: 32如果
iota
按照声明的行数来自增的话:1
2
3
4
5
6// a, a1 = iota(0), 1 << iota(0)
// b, b1 = iota(1), 1 << iota(1)
// c, c1 = iota(2), 1 << iota(2)
a: 0, a1: 1
b: 1, b1: 2
c: 2, c1: 4答案式第二种: 按照行维度, 递增, 实际输出为:
1 | [root@VM-0-9-centos learnGo]# go run ./cmd/main.go |
现在将通过源码的方式解读原理
源码
Go
编译器在编译阶段会将变量声明/常量声明转换为语法树节点, 其结构体定义为:
1 | // A ValueSpec node represents a constant or variable declaration |
转换过程如下:
- 解析常量表达式:编译器首先会解析源代码中的常量表达式,包括常量的名称、类型、值等信息。
- 创建
ValueSpec
结构体:根据常量表达式的信息,编译器会创建一个对应的ValueSpec
结构体实例。 - 填充字段信息:将常量表达式的各项信息填充到
ValueSpec
结构体的相应字段中,比如将常量的名称填充到Names
字段、常量的类型填充到Type
字段、常量的值填充到Values
字段等。 - 关联文档注释和行注释:如果常量表达式有关联的文档注释或行注释,编译器也会将这些注释信息填充到
Doc
和Comment
字段中。 - 生成语法树:最终,编译器会将创建好的
ValueSpec
结构体添加到整个语法树中,以便后续的语法分析、类型检查和代码生成等步骤。
那么当一行中具备多个常量时, 会生成一个ValueSpec
或是多个ValueSpecs
呢? 很明显, 由于ValueSpec
中Names
与Values
字段均为切片, 其会将一行中的多个常量声明, 定义在一个ValueSpec
中.
Ok
, 回到我们的问题, 为何iota
按行维度递增, 是因为其构造常量的算法如下所示:
1 | for iota, spec := range ValueSpecs { |
结论
从上面的伪算法代码中可以得知:
iota
是作为索引的形式构造的, 所以从0
开始;- 由于
ValueSpce
是以行维度构造的, 那么iota
也是按照行维度递增的.