# 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"
  )
  ```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gocompiler.shizhz.me/3.-golang-bian-yi-qi-yu-fa-fen-xi/3.3-shu-ju-jie-gou.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
