Compare commits

...

3 Commits

Author SHA1 Message Date
徐涛
44caae4fc8 post:暂时完成基本类型。 2023-09-12 16:04:05 +08:00
徐涛
9e78e85613 fix:修复admonish提示中的错误。 2023-09-11 17:22:36 +08:00
徐涛
deef0f833f post:基本完成标识符和关键字。 2023-09-10 21:45:19 +08:00
7 changed files with 433 additions and 4 deletions

View File

@@ -21,9 +21,11 @@
- [Go Module](./project-structure/module/index.md)
- [go.mod 的组织](./project-structure/module/mod-file.md)
- [依赖版本的发布](./project-structure/module/release.md)
- [Go 基本语法]()
- [基本数据类型]()
- [表达式]()
- [Go 基本语法](./grammar/index.md)
- [标识符与关键字](./grammar/identifier-keyword.md)
- [常量与变量](./grammar/constant-variable.md)
- [基本数据类型](./grammar/basic-types.md)
- [表达式](./grammar/expression.md)
- [语句]()
- [函数]()
- [指针与引用]()
@@ -36,6 +38,7 @@
- [结构体]()
- [接口]()
- [泛型]()
- [类型定义]()
- [错误处理]()
- [反射]()
- [并发]()

109
src/grammar/basic-types.md Normal file
View File

@@ -0,0 +1,109 @@
# 基本数据类型
Go 中的数据类型主要有布尔、数字、字符串、数组、切片、结构体、指针、函数、接口、Map 和通道。这一章中将主要介绍各个基本类型的语法定义和基本功能,其具体的使用方法将在后文中的独立章节中进行具体说明。
在 Go 中数据类型的语法定义如下:
```
Type = TypeName [ TypeArgs ] | TypeLit | "(" Type ")" .
TypeName = identifier | QualifiedIdent .
TypeArgs = "[" TypeList [ "," ] "]" .
TypeList = Type { "," Type } .
TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType | SliceType | MapType | ChannelType .
```
在这个语法定义中,`TypeArgs`表示一个类型参数,是后面索要介绍的泛型中使用的,目前可以暂时不必理会。目前需要从语法定义中了解的只有 Go 中常用的数据类型的书写方法。
## 布尔类型
布尔类型是 Go 语言中最简单也是使用最广的类型,其类型标识符为`bool`,可取值为预定义的常量`true``false`,分别对应真值和假值。
## 数字类型
数字类型是整型、浮点型、复数型三种类型的统称,它们分别用于表示整数、浮点值和复数值。用于表示数字类型的标识符都是 Go 语言预先定义好的,全局有效的。数字类型主要有以下这些。
| 类型标识符 | 指代类型 | 长度(位) | 最小值 | 最大值 |
| ------------ | ---------------------------- | ---------- | -------------------- | -------------------- |
| `uint8` | 无符号 8 位整型 | 8 | 0 | 255 |
| `uint16` | 无符号 16 位整型 | 16 | 0 | 65535 |
| `uint32` | 无符号 32 位整型 | 32 | 0 | 4294967295 |
| `uint64` | 无符号 64 位整型 | 64 | 0 | 18446744073709551615 |
| `int8` | 有符号 8 位整型 | 8 | -128 | 127 |
| `int16` | 有符号 16 位整型 | 16 | -32768 | 32767 |
| `int32` | 有符号 32 位整型 | 32 | -2147483648 | 2147483647 |
| `int64` | 有符号 64 位整型 | 64 | -9223372036854775808 | 9223372036854775807 |
| `float32` | IEEE--754 32 位浮点 | 32 | | |
| `float64` | IEEE-754 64 位浮点 | 64 | | |
| `complex64` | `float32`型实数和虚数复数 | 64 | | |
| `complex128` | `float64`型实数和虚数复数 | 128 | | |
| `byte` | 单字节整型,`uint8`别名 | 8 | | |
| `rune` | 字符型,`int32`别名 | 32 | | |
| `uint` | 机器架构长度无符号整型 | 32 或 64 | | |
| `int` | 机器架构长度有符号整型 | 32 或 64 | | |
| `uintptr` | 足以存放内存地址的无符号整型 | | | |
## 字符串类型
字符串类型是一个可能为空的字节序列,是一个字符值的集合。字符串类型使用`string`预定义标识符声明。字符串的内容一旦创建即不可更改,
字符串中的元素可以通过下标访问,其中元素的索引从 0 开始。
## 数组类型
数组类型是由一个单一类型元组组成的序列类型,数组中元素的数量称为数组的长度。数组类型的定义语法格式如下。
```
ArrayType = "[" ArrayLength "]" ElementType .
ArrayLength = Expression .
ElementType = Type .
```
根据数组类型的语法定义,一个数组类型可以使用`[7]int``[20]byte`来表示。这里需要注意的是,数组类型中的长度是数组类型的一部分,必须是能够确定的`int`类型非负常数,数组一旦定义,其长度在数组的作用域内就不再可变。数组总是一维的,但是可以通过组合来形成多维数组,例如`[4][5]int`
数组中的元素可以通过整型索引来访问。
## 切片类型
之前介绍过的数组类型是一个长度固定不可变的序列类型,切片则是一个不固定长度的序列类型。与数组一样,切片也是通过下标去访问其中元素的。切片类型的定义语法如下。
```
SliceType = "[" "]" ElementType .
```
根据切片类型的语法定义,一个切片类型可以使用`[]int``[]byte`来表示。与数组类型不同的是,切片类型的类型中没有切片长度的部分。但是与数组一样,切片也是一维的,并且可以通过组合形成多维切片,但是由于切片的长度是可以动态变化的,所以多维切片中的每一个内部切片都需要单独完成初始化。
## 结构体
结构体是一个由多种类型组合在一起形成的一个能够记录和描述复杂内容的类型。其中每一个命名元素都有自己端粒的类型,被称为字段。结构体的具体语法定义将放在后面专门讲述结构体的章节详细说明。
## 指针类型
指针无论在哪个语言中都是一个非常神奇和灵活的类型,它在理论上可以指向任意的内存地址。在 Go 中一个指针只能指向给定类型的变量地址,这个给定的类型被称为指针的基类型。指针类型的语法定义如下。
```
PointerType = "*" BaseType .
BaseType = Type .
```
根据指针类型的语法定义,一个指针类型可以使用`*int``*[]byte`的形式来表示。
## Map 类型
Map 类型主要功能是用来保存键值对是一种无序的序列由两种类型组成其中作为值的类型也被称为元素类型另一种则作为值的索引也被称为键类型。Map 类型的语法定义如下。
```
MapType = "map" "[" KeyType "]" ElmentType .
KeyType = Type .
```
根据 Map 类型的语法定义,以下 Map 类型的书写都是合法的:`map[string]int``map[string]iterface{}`
## 通道类型
Go 里提供的通道类型提供了一种机制,可以在不同的协程和线程之间传输指定类型的元素来实现通信。用于实现这种通信功能的类型就是通道。通道的语法定义如下。
```
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
```
具体通道类型的使用将在后续的独立章节中介绍。

View File

@@ -0,0 +1,178 @@
# 常量和变量
常量和变量是程序中所要操作的数据的载体。常量和变量的区别其实主要在于其中所承载的数据是否可变,数据的可变性决定了承载指代它们的容器,并且决定了它们在程序中能够发挥的作用。
## 常量
常量顾名思义就是不可变的量,在程序中定义的常量一般都携带有特殊的含义。根据常量类型的不同,一般可以按参照类型分为布尔常量、字符常量、整数常量、浮点常量、复数常量和字符串常量,其中字符常量、整数常量、浮点常量和复数常量一般统称为数字常量。
常量主要是通过字面量、其他的常量标识符、常量表达式等定义的,其定义原则就是用来定义常量的值在 Go 项目编译的时候是能够确定的,只要是能够符合这个条件的值,都可以用来定义常量。常量可以是类型化的也可以是非类型化的,像是字面量、`true``false``iota`或者是包含非类型化常量的常量表达式都是非类型化的。常量的类型可以在常量定义表达式中显式声明,也可以通过在赋值表达式中为变量赋值时隐式声明。
```admonish tip
隐式类型声明都是通过类型推断完成的。如果给定常量值不能匹配常量或者变量的类型那么Go将产生一个错误。如果目标类型是一个类型参数那么这个常量值将会被转换成匹配类型参数的非常量值。
非类型化的常量并不等于常量没有类型,非类型化只是指常量的类型是隐式声明的而已。
```
数字常量通常都标识任意精度的准确值,不会存在溢出的情况,所以不会存在负零、无穷大和非数字值的常量。尽管数字常量在 Go 中拥有任意精度,但是在编译器内它们的精度还是有限制的。
程序所使用的常量都是通过`const`关键字定义的,其语法定义如下:
```
ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] .
IdentifierList = identifier { "," identifier } .
ExpressionList = Expression { "," Expression } .
```
以下是一些常见的常量定义,以及它们所定义出的内容类型。
```go
const a = 2 + 3.0 // a = 5.0,非类型化浮点常量
const b = 15 / 4 // b = 3非类型化整型常量
const c = 15 / 4.0 // c = 3.75,非类型化浮点常量
const θ float64 = 3 / 2 // θ = 1.0,类型化双精度浮点常量,
const λ float64 = 3 / 2. // λ = 1.5,类型化双精度浮点常量,
const d = 1 << 3.0 // d = 8非类型化整型常量
const e = 1.0 << 3 // e = 8非类型化整型常量
const f = int32(1) << 33 // 非法常量将常量值赋予int32类型常量是发生了溢出
const g = float64(2) >> 1 // 非法常量float64(2)是一个类型化的常量
const h = "foo" > "bar" // h = true非类型化布尔常量
const j = true // j = true非类型化布尔常量
const k = 'w' + 1 // k = 'x',非类型化字符常量
const l = "hi" // l = "hi",非类型化字符串常量
const m = string(k) // m = "x",类型化字符串常量
const Σ = 1 - 0.7i // 非类型化复数常量
const Δ = Σ + 2.0e-4 // 非类型化复数常量
const φ = iota * 1i - 1 / 1i // 非类型化复数常量
const ic = complex(0, c) // ic = 3.75i,非类型化复数常量
const iθ = complex(0, θ) // iθ = 1i类型化复数常量
```
在上面这些示例中,阅读时清留意常量的类型化和非类型化的区别。
根据`const`语法定义,在定义常量的时候还可以通过常量列表来进行批量定义,例如:
```go
const a, b, c = 1, 3, "hi" // a = 1b = 3c = "hi",分别是非类型化整型常量和非类型化字符串常量
const u, v float32 = 0, 3 // u = 0.0v = 3.0,都是类型化单精度浮点常量
```
### iota
在使用带括号的常量声明列表时,用于定义常量的表达式可以从第二个常量(`ConstSpec`)处省略,这样形成的空列表就相当于前面第一个非空表达式列表及其类型。所以省略表达式列表就相当于重复之前的表达式列表。这种用法通常都会结合`iota`这个预定义标识符来使用。
`iota`标识连续的非类型化整数常量,它其实是一个索引,值从`0`开始,可以用来构建一组相关的常量。在每一个常量列表开始的时候,`iota`的索引都会被重置为`0`。
以下是几个利用`iota`定义常量列表的例子。
```go
const (
a = 1 << iota // a = 1此时iota = 0
b // b = 2此时iota = 1
c // c = 4此时iota = 2
)
const (
a = 1 << iota // a = 1
b = 1 << iota // b = 2
c = 3 // c = 3此处没有使用iota的值
d = 1 << iota // d = 8此时iota = 3
)
const a = iota // a = 0
const b = iota // b = 0每一个const定义序列开始iota的索引值就被重置了
```
如果在常量定义列表中,有一些值是不需要的,可以使用`_`来跳过,例如:
```go
const (
a = iota // a = 0
b // b = 1
_ // 空白标识符可以跳过某一个值的获取此处就跳过了iota = 2
d // d = 3
)
```
```admonish tip
注意Go中的开放性规则以上示例中的所有内容都是仅能在当前包中访问的也就是私有的。如果需要在其他包中可以访问常量的开头字母必须是大写例如`const A = 5`。
`const`关键字引领的常量定义是Go语言中的一等公民可以直接在包中声明。
```
## 变量
变量的主要用途是保存值的存储位置。是的,变量保存的不是值本身,而是值在内存中的位置,这里请不要是指针的概念混淆了。一个变量所能够指代的值是由其类型决定的。
Go 会在编译时对**变量声明**、**函数参数**、**函数返回结果**、**函数声明**、**函数签名**预留命名变量存储空间,而调用内置函数`new`或者获取一个复杂字面量的地址,都会在运行时为变量分配一个存储空间。
变量都是通过`var`关键字声明的,其语法定义如下:
```
VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
```
根据`var`关键字的语法定义,以下示例中的变量定义都是合法的。
```go
var i int // 变量i仅声明自动使用零值初始化
var U, V float64 // 可悲外部访问的变量U、V仅声明自动使用零值初始化
var k = 0 // 使用赋值表达式初始化变量kk的类型由提供的值推断
var x, y float64 = -1, -2 // 同时定义双精度浮点类型变量x和y并对其赋值
var (
a int
r, s, t = 2.0, 3, "world" // 利用赋值语句同时定义不同类型的变量
)
```
在上面的示例中,有一些变量在声明的时候没有初始化,此时这个变量是使用其零值进行默认初始化的。所以在声明变量的时候,需要注意每个类型的零值是什么样的。
变量声明中同样也可以使用`_`空白标识符来略过一些赋值过程中不需要的内容。例如在判断一个变量的类型的时:`var _, ok = x.(T)`。
```admonish tip
`var`关键字引领的变量定义是Go语言中的一等公民可以直接在包中声明。
```
### 简短变量定义
Go 除了可以使用`var`关键字声明和定义变量以外,还提供了一种简短的变量声明方式,这种简短的变量声明方式在 Go 语言代码中被广泛的使用。这种简短的变量声明语法定义如下。
```
ShortVarDecl = IdentifierList ":=" ExpressionList .
```
使用这种简短的变量定义方式,上面的变量定义示例就可以改写成以下样子。
```go
x, y := -1, -2
k := 0
r, s, t := 2.0, 3, "world"
_, ok := x.(T)
```
使用这种简短变量定义时需要注意的是,简短变量定义中不支持书写变量的类型,也就是说变量的类型是完全依靠推断的。而且与使用`var`关键字的变量声明不同,简短变量声明只能够使用在函数内部。
## 作用域
常量和变量的声明都是将一个非空白标识符绑定到常量和变量的过程,实际上这个声明的过程除了可以绑定常量和变量以外,还可以绑定类型、类型参数、函数、标签、包等,这些其他内容的声明会在介绍相应内容的时候进行说明。
```admonish caution
Go中所有声明的限制是同一个非空白标识符在同一个块中不能声明两次在同一个包的不同文件中也一样。空白标识符因为不会引入绑定所以它不是一个声明所以其使用次数不受限制。
```
一个标识符的作用范围是声明它的代码区块范围Go 语言使用`{}`来定义一个块。那么对于一个标识符来说,其作用域的确定主要依赖于以下几种规则。
1. Go 语言预定义标识符的作用域恒为程序全局。
1. 在函数之外定义的标识符,作用域为包(`package`)。
1. 被导入包的包名作用域是包含导入声明的文件。
1. 方法的接收器、函数参数、函数返回值变量的作用域是函数体。
1. 函数的类型参数的作用域是从方法接收器开始到函数体末尾。
1. 类型参数标识符的作用域是从类型名称开始直到类型定义结束。
1. 函数内部声明的常量和变量的作用域是从其声明结束开始到包含其声明的块的结束。
1. 函数内部声明的类型标识符的作用域是从类型声明开始直到包含其声明的块的结束。
```admonish tip "作用域遮罩"
如果在一个块的内部构建了一个新的块,而且在这个内部块内声明了与外部块中相同的标识符,那么内部块中声明的标识符就将在其作用域范围内屏蔽外部块的同名标识符,这种情况通常被称为作用域遮罩,也是在声明标识符的时候,比较容易被忽略的一个问题。
```

View File

@@ -0,0 +1 @@
# 表达式

View File

@@ -0,0 +1,97 @@
# 标识符和关键字
标识符和关键字在任何语言的程序中都是组成程序代码的基础元素,它们在程序代码中都有独特且专一的功能。
## 标识符
标识符在代码中主要用来命名程序实体,例如变量、类型等。在 Go 语言中,标识符是一个由一个或者多个字母或数字组成的序列,其第一个字符必须是字母,不能是数字。
标识符的语法定义为:
```
identifier = letter { letter | unicode_digit } .
```
在 Go 语言中,由一些标识符是在全局隐式定义的,它们已经具有了特殊含义的指代,不可被挪用于指代其他内容。以下表格中即是 Go 在全局定义的标识符。
| 标识符 | 类型 | 含义 |
| ------------ | ---- | -------------------------------------------------------- |
| `any` | 类型 | 指代任意类型 |
| `bool` | 类型 | 指代布尔类型 |
| `byte` | 类型 | 指代字节类型 |
| `comparable` | 类型 | 指代可比较类型 |
| `complex64` | 类型 | 指代 64 位复数类型 |
| `complex128` | 类型 | 指代 128 位复数类型 |
| `error` | 类型 | 指代描述错误的接口 |
| `float32` | 类型 | 指代 32 位单精度浮点类型 |
| `float64` | 类型 | 指代 64 位双精度浮点类型 |
| `int` | 类型 | 指代当前机器架构支持长度的整型 |
| `int8` | 类型 | 指代 8 位长度的单字节整型 |
| `int16` | 类型 | 指代 16 位长度的双字节整型 |
| `int32` | 类型 | 指代 32 位长度的四字节整型 |
| `int64` | 类型 | 指代 64 位长度的八字节整型 |
| `rune` | 类型 | 指代一个 UTF-8 码点字符 |
| `string` | 类型 | 指代字符串类型 |
| `uint` | 类型 | 指代当前机器架构支持长度的无符号整型 |
| `uint8` | 类型 | 指代 8 位长度的单字节无符号整型 |
| `uint16` | 类型 | 指代 16 位长度的双字节无符号整型 |
| `uint32` | 类型 | 指代 32 位长度的四字节无符号整型 |
| `uint64` | 类型 | 指代 64 位长度的八字节无符号整型 |
| `uintptr` | 类型 | 指代一个用于保存指针地址的整型 |
| `true` | 常量 | 指代真值 |
| `false` | 常量 | 指代假值 |
| `iota` | 常量 | 指代常量序列计数起始的`0` |
| `nil` | 零值 | 指代空指针 |
| `append` | 函数 | 用于向一个切片中增加元素 |
| `cap` | 函数 | 返回一个切片的容量capacity |
| `clear` | 函数 | 清空 map重置切片中的全部元素Go 1.21 以后可用) |
| `close` | 函数 | 用于关闭通道 |
| `complex` | 函数 | 用于构建一个复数类型 |
| `copy` | 函数 | 用于复制一个切片 |
| `delete` | 函数 | 用于删除 map 中的键值对和切片中的元素 |
| `imag` | 函数 | 获得一个复数的虚数部分 |
| `len` | 函数 | 计算字符串或者切片、数组等可被计数内容的长度(元素数量) |
| `make` | 函数 | 创建一个切片、Map、结构体的实例 |
| `max` | 函数 | 获取给定值中的最大值Go 1.21 以后可用) |
| `min` | 函数 | 获取给定值中的最小值Go 1.21 以后可用) |
| `new` | 函数 | 创建一个指定类型的实例并返回其引用 |
| `panic` | 函数 | 中断程序运行并抛出异常 |
| `println` | 函数 | 在控制台中输出指定内容 |
| `real` | 函数 | 获得一个复数的实数部分 |
| `recover` | 函数 | 捕获`panic`抛出的异常,并使程序从异常状态中恢复 |
### 空白标识符
空白标识符在之前引入包的章节中就已经见识过了,空白标识符就是一个`_`,它在表达式中充当匿名占位符,在声明表达式、赋值表达式中都具有特殊的含义。
## 关键字
关键字实际上也是标识符但是是语言中保留的内容因为其在语言中具有专门的用途和意义所以不能被用作普通的标识符。Go 中的关键字主要有以下这些。
| 关键字 | 功能 |
| ------------- | ---------------------------------------- |
| `break` | 用于退出循环 |
| `case` | 用于定义`switch`语句的分支条件 |
| `chan` | 用于标识通道类型 |
| `const` | 用于定义常量 |
| `continue` | 用于提前结束当前的循环,并开始下一次循环 |
| `default` | 用于定义`switch``select`中的默认分支 |
| `defer` | 用于定义延后执行的语句 |
| `else` | 用于定义`if`语句的分支 |
| `fallthrough` | 用于打通`switch`语句中的多个`case`分支 |
| `for` | 用于定义循环 |
| `func` | 用于定义函数 |
| `go` | 用于启动协程 |
| `goto` | 用于执行强制跳转 |
| `if` | 用于定义条件分支 |
| `import` | 用于引入其他的包 |
| `interface` | 用于定义接口 |
| `map` | 用于定义 map 类型 |
| `package` | 用于定义当前代码文件归属的包 |
| `range` | 用于定义范围边界 |
| `return` | 用于从函数中返回值 |
| `select` | 用于监听和响应通道 |
| `struct` | 用于定义结构体 |
| `switch` | 用于定义复杂分支结构 |
| `type` | 用于定义类型 |
| `var` | 用于定义变量 |

41
src/grammar/index.md Normal file
View File

@@ -0,0 +1,41 @@
# Go 基本语法
任何一门编程语言都是由一些基本的词汇元素组成的Go 语言也不例外。构成 Go 语言程序代码的基本元素归类起来主要是标识符、关键字、操作符、标点符号和文字五类。其中所有的空白字符包括空格、Tab 制表符、回车等,都会在程序编译时被忽略,它们只是程序代码中基本元素的分隔符,而不是代码的基本元素之一。
## 分号
在在大多数语言中,分号都是用来作为一个语句结束的标记的,在 Go 语言中也不例外,但是 Go 提供了一些独特的解析规则使得我们在大部分情况下可以省略分号的书写。
1. 当一行的内容被分解为 Token 时,最后一个 Token 是以下内容的时候,分号会被自动插入。
- 一个标识符;
- 一个整型、浮点型、复数类型、字符或者是字符串的字面量;
- `break``continue``fallthrough`或者是`return`中的一个;
- `++``--``)``]`或者是`}`中的一个。
1. 一个在`)`或者`}`之前的独占一行的复杂语句。
基于这两条规则,绝大多数语句结尾的分号都可以被省略掉,读者可以在实际编程练习中仔细体会一下。
## 代码文档字符集
Go 语言的代码文档是采用 UTF-8 编码格式的,所以在代码文档中基本上可以使用任意合法的 UTF-8 字符。但是 Go 语言在其 EBNF 格式的定义中,还是将代码文档中可以使用的字符划分成了以下几类:
- 换行符,在 EBNF 定义中使用`newline`标识。
- UTF-8 字符,是指所有除了换行符以外的 UTF-8 码位,在 EBNF 定义中使用`unicode_char`标识。
- UTF-8 字母,是指 UTF-8 码位中,所有被归类为字母的码位,在 EBNF 定义中使用`unicode_letter`标识。
- UTF-8 数字,是指 UTF-8 码位中,所有被归类为数字或者数字位的码位,在 EBNF 定义中使用`unicode_digit`标识。
通过以上对于代码文档中可以使用的字符的定义,再结合 Go 语言代码中出现的内容,就可以定义出也以下基本字符和数字。
```
letter = unicode_letter | "_" .
decimal_digit = "0" ... "9" .
binary_digit = "0" | "1" .
octal_digit = "0" ... "7" .
hex_digit = "0" ... "9" | "A" ... "F" | "a" ... "f" .
```
这套定义也就是说,在 Go 语言代码中,所有的合法字母除了包括 UTF-8 中被归类为字母的码位以外,还包括下划线`_`。剩下的四个定义分别对应十进制数字、二进制数字、八进制数字和十六进制数字。
```admonish tip
这些基础的EBNF定义虽然简单但是它是构成后文中其他复杂EBNF定义的基础。
```

View File

@@ -66,7 +66,7 @@ Go 要求模块的版本标识遵守[Semantic Versioning 2.0.0(语义版本)
当开始一个新的主版本号的变化时,模块路径也需要随之发生一些变化,其中最明显的一个变化就是模块路径上需要增加主版本号后缀。这个主版本号后缀非常容易理解,例如依赖库`example.com/mod`是库的第一版的模块路径的话,那么第二版的模块路径会变成`example.com/mod/v2`。
```admonish tip 模块引入兼容规则
```admonish tip "模块引入兼容规则"
如果新库的模块路径与旧库的模块路径相同,那么新库旧必须与旧库完全兼容。
```