> For the complete documentation index, see [llms.txt](https://gocompiler.shizhz.me/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gocompiler.shizhz.me/3.-golang-bian-yi-qi-yu-fa-fen-xi/3.3-shu-ju-jie-gou.md).

# 3B.3 数据结构

语法树相关的数据结构都在文件 `$GCROOT/compile/internal/syntax/nodes.go` 中，每个源文件对应一个如下结构体的实例：&#x20;

```go
// package PkgName; DeclList[0], DeclList[1], ...
type File struct {
    Pragma   Pragma // 编译指令，嵌入在源代码的注释中，形如：go://xxxx, 可以忽略不予考虑
    PkgName  *Name  // 该文件的包名
    DeclList []Decl // 该文件中包含的顶级声明
    EOF      Pos
    node     // 内嵌结构体，使 File 实现 nodes.go 中定义的 Node 接口
}
```

该结构是`syntax.Parse()`函数的返回结果，代表着一个源文件的 AST。其中重要的是 `DeclList`, 其包含了该源文件中所有的声明，声明（Declaration）是 Go 源文件顶级作用域中的唯一合法结构，声明在其内部递归地包含其他结构体。

该文件中定义的各种数据结构与 [Go 语言规范](https://golang.org/ref/spec) 中所定义的语言结构是一一对应的，在源代码中，很多数据结构的声明上面都有对应的文法。所有的数据结构可以归为如下几类：

### 3.3.1 声明（Declaration） <a href="#org28a536d" id="org28a536d"></a>

声明就是在程序里面说“我要创建一个 X, 他的名字是 Y”，其包含两件事情：一个是命名，一个是绑定。例如 Go 中的 `var name string` 就是一个变量声明，其在程序中取了一个名字：name, 然后将其与 string 这种类型绑定起来，翻译成前边的语言就是：我要创建一个字符串，他的名字是 name。注意 `name := "Golang"` 不仅包含了声明，而且包含了赋值语句（Statement），只是声明中的类型信息是隐式的。

[Go Spec](https://golang.org/ref/spec#Declarations_and_scope) 中对声明的阐述为：

> &#x20;A declaration binds a non-blank identifier to a constant, type, variable, function, label, or package. Every identifier in a program must be declared. No identifier may be declared twice in the same block, and no identifier may be declared in both the file and package block.

由此可见，声明其实就是创建程序组件并为其命名的操作，有了这些程序组件作为积木，我们才能搭建出整个程序大厦。在 Go 语言源文件中，合法的顶级声明包括：常量、类型、变量、函数、方法 以及 package, 下面都是合法的声明：

```go
type A struct{}    // 声明一个结构体类型，其名字是 A
type B interface{} // 声明一个接口类型，其名字是 B

const a = 1 // 声明一个常量，将其命名为 a，并将其初始化为 1. 因为常量值无法修改，所以常量的声明必须与初始化在一条语句内完成。

var b string // 声明一个变量，将其命名为 b

func sum(i, j int) int // 声明一个函数，将其命名为 sum, 没有函数体。可以通过编译指令 `go:linkname` 将其与其他地方的函数链接起来
func sub(i, j int) int { // 声明一个函数，将其命名为 sub, 并包含函数体
    return i - j
}

type Str string        // 声明一个类型 Str, 其 underlying type 是 string
type StrAlias = string // 声明一个类型别名 StrAlias，其与 string 类型等价
```

在 nodes.go 中，每一种声明都有对应的数据结构，作为示例，我们在这里仅仅看一下函数声明的结构：

```go
// func          Name Type { Body }
// func          Name Type
// func Receiver Name Type { Body }
// func Receiver Name Type
type FuncDecl struct {
    Pragma     Pragma
    Recv       *Field     // 如果该函数是一个方法，则表示 Receiver, 否则为 nil
    Name       *Name      // 函数名，是一个表达式（Expr）
    TParamList []*Field   // 类型参数，即泛型
    Type       *FuncType  // 函数类型，是一个表达式（Expr），其中包含函数的参数与返回值的列表
    Body       *BlockStmt // 函数体的语句（Statement），如果没有函数体则为空
    decl                  // 内嵌结构体，使该结构体实现接口 Decl
}
```

可以发现该结构体就是对文法的一种直接映射，其每个属性也代表了一个函数或者方法的某个代码块。

结构体 FuncDecl 可以表示函数、方法两类声明，实际上 Go 中函数与方法没有本质区别，方法与函数的区别在于方法的第一个参数默认绑定到调用的实例上，这与 Java 的 `this` 关键字是一样的。因为 Go 中函数是一类公民（First Class Citizen），所以我们甚至可以将方法赋值给变量，如下代码体现了方法与函数的等价性质：

```go
package main

import "fmt"

type people struct {
    name string
}

func (this people) greeting(friend string) {
    fmt.Println("My name is ", this.name, ", How are you ", friend)
}

func main() {
    f := people.greeting
    p := people{"Golang"}

    f(p, "Java") // 与 f.greeting("Java") 效果一样
}
```

### 3.3.2 表达式（Expression） <a href="#org062dc7b" id="org062dc7b"></a>

表达式代表的是可以对其进行求值运算的代码块，例如算术运算、函数调用、访问数组或者哈希表等。

[Go Spec](https://golang.org/ref/spec#Expressions) 中对表达式的阐述为：

> &#x20;An expression specifies the computation of a value by applying operators and functions to operands.

也就是说表达式代表的是一个计算，该计算过程通过将某种运算符（operator）应用到操作数（operand）、或者将函数应用到其参数上来完成。常见的运算符包括算术运算符、逻辑运算符、位运算符等，我们对使用这类运算符做“计算”的认知是很自然的，例如表达式 `i + j`, 运算符（Operator）是 `+`, 操作数（Operand）是 `i` 与 `j`. 但对于编译器而言，运算符的范围更加宽泛，例如赋值语句的右边只能是一个表达式，形如 `a := b` 中的 `b` 必须有值返回才能将其赋给 `a`. 例如如下代码：

```go
language := struct {
    Name string
    Age  int
}{
    Name: "Golang",
    Age:  100,
}
```

赋值语句右侧以关键字 `struct` 开始，所以对于编译器而言 `struct` 就是一个运算符，其操作数就是紧跟着的 `{}` 所包围的代码块，编译器根据该运算符“计算”出了一个类型值，然后为该类型值初始化了一个实例赋值给 `language`.

nodes.go 中定义的表达式类型比较多，包括如下种类：

1. &#x20;字面量 \
   所有的字面量都通过一个表达式来表示：

   ```
   type BasicLit struct {
       Value string  // 字面量
       Kind  LitKind // 字面量类型，包括整数、浮点数、字符串等
       Bad   bool    // true means the literal Value has syntax errors
       expr
   }
   ```
2. &#x20;数据访问 \
   例如访问数组元素、访问 map、访问 struct 的字段等。形如 t\[i] 通过下标访问数据的表达式定义如下：

   ```
   // X[Index]
   type IndexExpr struct {
       X     Expr // 被访问的对象，通过一个表达式来表示
       Index Expr // 下标，也通过一个表达式来表示
       expr
   }
   ```
3. &#x20;类型定义 \
   所有的类型定义都是表达式，这里我们仅仅看一下 struct 类型的表达式结构：

   ```
   // struct { FieldList[0] TagList[0]; FieldList[1] TagList[1]; ... }
   type StructType struct {
       FieldList []*Field    // 该 struct 内部所有字段的列表
       TagList   []*BasicLit // 每个字段的 tag, 通过下标与字段对应
       expr
   }
   ```

   &#x20;类型表达式在类型检查时会用来构建具体的类型对象

这里只是简单列举了几类表达式，所有的表达式定义详见源文件。

### 3.3.3 语句（Statement） <a href="#orga19437e" id="orga19437e"></a>

如果说表达式是程序的计算单元的话，那么语句就是程序的控制单元。

[Go Spec](https://golang.org/ref/spec#Expressions) 中对语句的阐述为：

> &#x20;Statements control execution.

例如 for 控制循环，if…else… 控制分支选项等。我们看一下 if 语句的结构：

```go
type IfStmt struct {
    Init SimpleStmt // if 语句的初始化语句，本身是一个语句
    Cond Expr       // 判断条件，一个返回值为 bool 的表达式
    Then *BlockStmt // 条件为 true 时执行的语句块
    Else Stmt       // 条件为假时执行的语句块，可以是 nil, *IfStmt, 或者 *BlockStmt 之一
    stmt
}
```

其他语句对应的结构体定义详见源文件。

### 3.3.4 其他 <a href="#org2ac81ee" id="org2ac81ee"></a>

除了表示程序块的数据结构，该文件内还定义了一些辅助结构，包括：

* 针对不同类型的几个接口，例如 `Node`, `Decl`, `Expr`, `Stmt` 等
* &#x20;Group

  ```
  // All declarations belonging to the same group point to the same Group node.
  type Group struct {
      _ int // not empty so we are guaranteed different Group instances
  }
  ```

  通过括号 `()` 括起来的一组声明都指向同一个 Group 实例。例如如下代码中所有的常量声明都会指向一个 Group 实例：

  ```
  const (
      name   = "Golang"
      friend = "Java"
  )
  ```
