0%

Go Data Structure Source Code: iota

参考: 1.5 iota · 《GO专家编程》 (kilvn.com)

iota常用于枚举值定义场景, 使用也颇为广泛. 本文将简单介绍下iota的实现原理.

引言

iotaGo预定义的标识符, 其会按照递增的顺序自动生成一组相关值, 起始为0.当 iota 在常量声明中被使用时,它会自动递增,直到遇到一个新的常量声明或 const 关键字。下面是一个简单的例子:

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

import "fmt"

const (
a = iota // a = 0
b // b = 1
c // c = 2
)

func main() {
fmt.Println(a, b, c) // 输出: 0 1 2
}

依次输出0 , 1, 2;

那么如果一行中出现多个iota相关的表达式呢? 例如:

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

import "fmt"

const (
a, a1 = iota, 1 << iota
b, b1
c, c1
)

func main() {
fmt.Printf("a: %v, a1: %v \n", a, a1)
fmt.Printf("b: %v, b1: %v \n", b, b1)
fmt.Printf("c: %v, c1: %v \n", c, c1)
}

上面的输出内容是什么?

  • 如果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
2
3
4
[root@VM-0-9-centos learnGo]# go run ./cmd/main.go 
a: 0, a1: 1
b: 1, b1: 2
c: 2, c1: 4

现在将通过源码的方式解读原理

源码

Go编译器在编译阶段会将变量声明/常量声明转换为语法树节点, 其结构体定义为:

1
2
3
4
5
6
7
8
9
10
// A ValueSpec node represents a constant or variable declaration
// (ConstSpec or VarSpec production).
//
ValueSpec struct {
Doc *CommentGroup // associated documentation; or nil
Names []*Ident // value names (len(Names) > 0)
Type Expr // value type; or nil
Values []Expr // initial values; or nil
Comment *CommentGroup // line comments; or nil
}

转换过程如下:

  1. 解析常量表达式:编译器首先会解析源代码中的常量表达式,包括常量的名称、类型、值等信息。
  2. 创建ValueSpec结构体:根据常量表达式的信息,编译器会创建一个对应的ValueSpec结构体实例。
  3. 填充字段信息:将常量表达式的各项信息填充到ValueSpec结构体的相应字段中,比如将常量的名称填充到Names字段、常量的类型填充到Type字段、常量的值填充到Values字段等。
  4. 关联文档注释和行注释:如果常量表达式有关联的文档注释或行注释,编译器也会将这些注释信息填充到DocComment字段中。
  5. 生成语法树:最终,编译器会将创建好的ValueSpec结构体添加到整个语法树中,以便后续的语法分析、类型检查和代码生成等步骤。

那么当一行中具备多个常量时, 会生成一个ValueSpec或是多个ValueSpecs呢? 很明显, 由于ValueSpecNamesValues字段均为切片, 其会将一行中的多个常量声明, 定义在一个ValueSpec中.

Ok, 回到我们的问题, 为何iota按行维度递增, 是因为其构造常量的算法如下所示:

1
2
3
4
5
6
for iota, spec := range ValueSpecs {
for i, name := range spec.Names {
obj := NewConst(name, iota...) //此处将iota传入,用于构造常量
...
}
}

结论

从上面的伪算法代码中可以得知:

  • iota是作为索引的形式构造的, 所以从0开始;
  • 由于ValueSpce是以行维度构造的, 那么iota也是按照行维度递增的.