参考: 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也是按照行维度递增的.