├── GO_OS_ARCH.md
├── README.md
├── gitflow.md
├── gogrammar.md
└── img
├── git0.png
├── git1.png
├── git2.png
├── git3.png
├── type_constraint.png
└── type_parameter.png
/GO_OS_ARCH.md:
--------------------------------------------------------------------------------
1 | # Golang supported OS/Arch table `go1.24`
2 |
3 | ## GOOS GOARCH
4 |
5 | | Architecture | GOOS →
GOARCH↓ | `aix` | `android` | `darwin` | `dragonfly` | `freebsd` | `illumos` | `ios` | `js` | `linux` | `netbsd` | `openbsd` | `plan9` | `solaris` | `wasip1` | `windows` |
6 | |:---------------------------------|:-----------------:|:--------:|:---------:|:--------:|:-----------:|:---------:|:---------:|:--------:|:--------:|:--------:|:--------:|:---------:|:--------:|:---------:|:--------:|:---------:|
7 | | x86 32-bit | `386` | | ✔ | | | ✔ | | | | ✔ | ✔ | ✔ | ✔ | | | ✔ |
8 | | x86 64-bit | `amd64` | | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | ✔ | ✔ | ✔ | ✔ | ✔ | | ✔ |
9 | | ARM 32-bit | `arm` | | ✔ | ✔ | | ✔ | | ✔ | | ✔ | ✔ | ✔ | ✔ | | | ✔ |
10 | | ARM 64-bit | `arm64` | | ✔ | ✔ | | ✔ | | ✔ | | ✔ | ✔ | ✔ | | | | ✔ |
11 | | LoongArch 64-bit | `loong64` | | | | | | | | | ✔ | | | | | | |
12 | | MIPS 32-bit,
big-endian | `mips` | | | | | | | | | ✔ | | | | | | |
13 | | MIPS 64-bit,
big-endian | `mips64` | | | | | | | | | ✔ | | | | | | |
14 | | MIPS 64-bit,
little-endian | `mips64le` | | | | | | | | | ✔ | | | | | | |
15 | | MIPS 32-bit,
little-endian | `mipsle` | | | | | | | | | ✔ | | | | | | |
16 | | PowerPC 64-bit,
big-endian | `ppc64` | ✔ | | | | | | | | ✔ | | ✔ | | | | |
17 | | PowerPC 64-bit,
little-endian | `ppc64le` | | | | | | | | | ✔ | | | | | | |
18 | | RISC-V 64-bit | `riscv64` | | | | | ✔ | | | | ✔ | | ✔ | | | | |
19 | | IBM ESA/390 | `s390x` | | | | | | | | | ✔ | | | | | | |
20 | | WebAssembly | `wasm` | | | | | | | | ✔ | | | | | | ✔ | |
21 |
22 | ## script for print Markdown table
23 |
24 | ```bash
25 | #!/bin/bash
26 |
27 | DIST=$(go tool dist list)
28 | OSes=$(go tool dist list | awk -F '/' '{print $1}' | sort -u)
29 | ARCHes=$(go tool dist list | awk -F '/' '{print $2}' | sort -u)
30 |
31 | # print table header
32 | echo '| GOOS →
GOARCH↓|`'$OSes'`|' | sed 's/\ /`|`/g'
33 |
34 | # print separator
35 | echo -n '|:--------:|'
36 | for o in $OSes; do
37 | echo -n ':------:|'
38 | done
39 | echo
40 |
41 | # print table body
42 | for a in $ARCHes; do
43 | printf '|%-10s' '`'$a'`'
44 | for o in $OSes; do
45 | if `echo $DIST | grep "$o/$a" &> /dev/null`; then
46 | echo -n '|✔'
47 | else
48 | echo -n '| '
49 | fi
50 | done
51 | echo '|'
52 | done
53 | ```
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #### 学习过程中记录下来的笔记
2 |
3 |
4 | [Go语言语法详解笔记](https://github.com/yougg/gonote/blob/main/gogrammar.md)
5 | 可以帮助新接触的朋友快速熟悉理解Golang,也可以作为查询手册翻阅。
6 |
--------------------------------------------------------------------------------
/gitflow.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | #### **◆ Fork代码步骤**
4 |
5 | 
6 |
7 | 1. 进入项目代码主线仓库, 点击`Fork`按钮,再点击用户创建新的Fork仓库.
8 |
9 | 2. 复制Fork的仓库路径(如: http://github.com/username/project.git)
10 | 在本地使用git客户端`clone`此分支代码.
11 | 如果当前已经clone了主线仓库的代码,则按如下方式切换到Fork的仓库.
12 | - 打开`Git Bash`进入本地代码仓库目录
13 |
14 | ```bash
15 | cd /home/cxy/go/src/myproject/
16 | ```
17 | - 切换本地仓库对应的远程路径为Fork仓库的路径
18 |
19 | ```bash
20 | git remote set-url origin http://github.com/username/project.git
21 | ```
22 | - 查看本地仓库对应的路径是否更新
23 |
24 | ```bash
25 | git remote -v
26 | ```
27 |
28 | 3. 切换本地仓库代码分支
29 | - 首先更新代码(确保本地所有代码已提交,没有未合并的分支)
30 |
31 | ```bash
32 | git fetch
33 | ```
34 | - 切换分支
35 |
36 | ```bash
37 | git checkout -b develop origin/develop
38 | ```
39 |
40 | 
41 |
42 | #### **◆ 从主线仓库更新代码到本地Fork仓库的分支步骤**
43 |
44 | 1. 增加主线代码仓库地址
45 | 打开`Git Bash`进入本地代码仓库目录执行
46 | ```bash
47 | git remote add trunk http://github.com/mygroup/project.git
48 | ```
49 |
50 | 2. 查看本地仓库对应的路径是否更新
51 | ```bash
52 | git remote -v
53 | ```
54 | 有类似如下输出:
55 | >origin http://github.com/username/project.git (fetch)
56 | origin http://github.com/username/project.git (push)
57 | trunk http://github.com/mygroup/project.git (fetch)
58 | trunk http://github.com/mygroup/project.git (push)
59 |
60 | 2. 从主线仓库origin/develop分支
61 | 更新代码到本地Fork仓库的origin/develop分支
62 | - 在`IDEA`中更新
63 | 1. 选中项目,点击右键`Git`菜单,选择`Repository` -> `Pull...`
64 | 2. 选择`Remote`为新增的主线远程仓库路径
65 | 3. 选择分支为`origin/develop`,然后点击`Pull`拉取代码进行更新.
66 | 4. 如果代码存在冲突,在提示合并窗口中进行合并操作.
67 | - 在命令行更新
68 |
69 | ```bash
70 | git pull http://github.com/mygroup/project.git trunk develop
71 | ```
72 |
73 | 
74 |
75 | #### **◆ 合并代码到主线仓库步骤**
76 |
77 | 1. 本地修改提交代码, 使用IDEA界面`Ctrl + k`提交或者命令`git commit`
78 |
79 | 2. 推送本地代码到Fork仓库,使用IDEA界面`Ctrl + Shift+ k`推送或者命令`git push`
80 |
81 | 3. 在git服务器上`Fork`的工程中新建`Merge Request`合并请求
82 | `Source branch`选择用户Fork仓库下的`develop`分支
83 | `Target branch`选择主线仓库下的`develop`分支
84 | 点击`Compare branches`进行对比,完善信息后提交合并请求
85 |
86 | 4. `Committer`接受合并请求,合入代码.
87 |
88 | #### **◆ 主线代码仓库发布版本步骤**
89 |
90 | 1. `Committer`在git服务器上主线代码仓库新建`Merge Request`合并请求
91 | `Source branch`选择主线仓库下的`develop`分支
92 | `Target branch`选择主线仓库下的`master`分支
93 | 点击`Compare branches`进行对比,完善信息后提交合并请求
94 |
95 | 2. 在CI服务器更新最新版本代码
96 | - 编译daily版本
97 |
98 | > 切换代码分支到`develop`
99 | - 编译release版本
100 |
101 | > 切换代码分支到`master`
102 |
--------------------------------------------------------------------------------
/gogrammar.md:
--------------------------------------------------------------------------------
1 | ## Google Go语言 golang 语法详解笔记
2 |
3 | | *Title* | Go Grammar Note |
4 | |-----------:|:----------------------------------------------------------------------------|
5 | | *Author* | yougg |
6 | | *Date* | 2025-02-17 |
7 | | *Version* | 1.24.0 |
8 | | *Source* | [Fork me on GitHub](https://github.com/yougg/gonote) |
9 | | *Describe* | 学习Go语言过程中记录下来的语法详解笔记,可以帮助新接触的朋友快速熟悉理解Golang,也可以作为查询手册翻阅,若有错误请在GitHub提issue。 |
10 |
11 | ---
12 |
13 | - [包 Package](#包-package)
14 |
15 | - [包的声明 Declare](#包的声明-declare)
16 | - [包的导入 Import](#包的导入-import)
17 | - [包内元素的可见性 Accessibility](#包内元素的可见性-accessibility)
18 |
19 | - [数据类型 Data Type](#数据类型-data-type)
20 |
21 | - [基础数据类型 Basic data type](#基础数据类型-basic-data-type)
22 | - [变量 Variable](#变量-variable)
23 | - [常量 Constant](#常量-constant)
24 | - [数组 Array](#数组-array)
25 | - [切片 Slice](#切片-slice)
26 | - [字典/映射 Map](#字典映射-map)
27 | - [结构体 Struct](#结构体-struct)
28 | - [指针 Pointer](#指针-pointer)
29 | - [通道 Channel](#通道-channel)
30 | - [接口 Interface](#接口-interface)
31 | - [自定义类型](#自定义类型)
32 |
33 | - [语句 Statement](#语句-statement)
34 |
35 | - [分号/括号 ; {](#分号括号--)
36 | - [条件语句 if](#条件语句-if)
37 | - [分支选择 switch](#分支选择-switch)
38 | - [循环语句 for](#循环语句-for)
39 | - [通道选择 select](#通道选择-select)
40 | - [延迟执行 defer](#延迟执行-defer)
41 | - [跳转语句 goto](#跳转语句-goto)
42 | - [阻塞语句 blocking](#阻塞语句-blocking)
43 |
44 | - [函数 Function](#函数-function)
45 |
46 | - [函数声明 Declare](#函数声明-declare)
47 | - [函数闭包 Closure](#函数闭包-closure)
48 | - [内建函数 Builtin](#内建函数-builtin)
49 | - [初始化函数 init](#初始化函数-init)
50 | - [方法 Method](#方法-method)
51 |
52 | - [并发 Concurrency](#并发-concurrency)
53 |
54 | - [测试 Testing](#测试-testing)
55 |
56 | - [单元测试 Unit](#单元测试-unit)
57 | - [基准测试 Benchmark](#基准测试-benchmark)
58 | - [模糊测试 Fuzzing](#模糊测试-fuzzing)
59 |
60 | - [泛型 Generics](#泛型-generics)
61 |
62 | - [类型约束](#类型约束-constraint)
63 | - [类型参数](#类型参数-parameters)
64 |
65 | ---
66 |
67 | ## **包 Package**
68 |
69 | ### **包的声明, keyword:`package`**
70 |
71 | - 使用`package`关键字声明当前源文件所在的包
72 | 包声明语句是所有源文件的第一行非注释语句
73 | 包名称中不能包含空白字符
74 | 包名推荐与源文件所在的目录名称保持一致
75 | 每个目录中只能定义一个package
76 |
77 | ```go
78 | package abc // 声明一个名为“abc”的包
79 | ```
80 | ```go
81 | package 我的包 // 声明一个名为“我的包”的包
82 | ```
83 | ```go
84 | package main // main包, 程序启动执行的入口包
85 | ```
86 |
87 | 错误的包声明
88 | ```go
89 | package "mypkg" // ❌错误
90 | ```
91 | ```go
92 | package a/b/c // ❌错误
93 | ```
94 | ```go
95 | package a.b.c // ❌错误
96 | ```
97 |
98 | ### **包的导入, keyword:`import`**
99 |
100 | - 导入包路径是对应包在如下列表中的相对路径
101 |
102 | - `$GOROOT/src/`
103 | - `$GOPATH/src/`
104 | - `$GOPATH/pkg/mod/`
105 | - `当前路径`
106 | - `go.mod`模块名声明中的相对路径
107 |
108 | 导入`$GOROOT/src/`中的相对路径包(官方标准库)
109 |
110 | ```go
111 | import "fmt"
112 | import "math/rand"
113 | ```
114 |
115 | 导入`$GOPATH/src/`或`$GOPATH/pkg/mod/`中的相对路径包
116 |
117 | ```go
118 | import "github.com/user/project/pkg"
119 | import "code.google.com/p/project/pkg"
120 | ```
121 |
122 | 导入当前包的相对路径包 `Deprecated`
123 | 例如有Go目录如下:
124 | `MODULE`
125 | ├─x0
126 | │ ├─y0
127 | │ │ └─z0
128 | │ └─y1
129 | │ └─z1
130 | └─x1
131 | └─y2
132 |
133 | ```go
134 | import "./y0/z0" // 在x0包中导入子包 z0包
135 | import "../y0/z0" // 在y1包中导入子包 z0包
136 | import "x0/y1/z1" // 在y2包中导入 z1包
137 | ```
138 |
139 | 错误的导入包路径
140 |
141 | ```go
142 | import a/b/c // ❌错误
143 | import "a.b.c" // ❌错误
144 | import a.b.c // ❌错误
145 | ```
146 |
147 | - 用圆括号组合导入包路径
148 |
149 | ```go
150 | import ("fmt"; "math")
151 |
152 | import (
153 | "fmt"
154 | "math"
155 | )
156 | ```
157 |
158 | - 导入包可以定义别名,防止同名称的包冲突
159 |
160 | ```go
161 | import (
162 | "a/b/c"
163 |
164 | c1 "x/y/c" // 将导入的包c定义别名为 c1
165 |
166 | 格式化 "fmt" // 将导入的包fmt定义别名为 格式化
167 |
168 | m "math" // 将导入的包math定义别名为 m
169 | )
170 | ```
171 |
172 | - 引用包名是导入包路径的最后一个目录中定义的唯一包的名称
173 | 定义的包名与目录同名时,直接引用即可
174 |
175 | ```go
176 | // 引用普通名称的导入包
177 | c.hello()
178 |
179 | // 引用定义别名的包
180 | 格式化.Println(m.Pi)
181 | ```
182 |
183 | 定义的包名与所在目录名称不同时,导入包路径仍为目录所在路径,引用包名为定义的包名称
184 |
185 | ```go
186 | // Package util
187 | // 源文件路径: proj/my-util/util.go
188 | package util
189 | ```
190 | ```go
191 | // 导入util包路径
192 | import "proj/my-util"
193 |
194 | // 引用util包
195 | util.doSomething()
196 | ```
197 |
198 | - 静态导入,在导入的包路径之前增加一个小数点`.`
199 |
200 | ```go
201 | // 类似C中的include 或Java中的import static
202 | import . "fmt"
203 |
204 | // 然后像使用本包元素一样使用fmt包中可见的元素,不需要通过包名引用
205 | Println("no need package name")
206 | ```
207 |
208 | - 导入包但不直接使用该包,在导入的包路径之前增加一个下划线`_`
209 |
210 | ```go
211 | // 如果当前go源文件中未引用过log包,将会导致编译错误
212 | import "log" // ❌错误
213 | import . "log" // ❌静态导入未使用同样报错
214 |
215 | // 在包名前面增加下划线表示导入包但是不直接使用它,被导入的包中的init函数会在导入的时候执行
216 | import _ "github.com/go-sql-driver/mysql"
217 | ```
218 |
219 | ### **包内元素的可见性 Accessibility**
220 |
221 | - 名称首字符为[Unicode包含的大写字母](http://www.fileformat.info/info/unicode/category/Lu/list.htm)的元素是被导出的,对外部包是可见的
222 | 首字为非大写字母的元素只对本包可见(同包跨源文件可以访问,子包不能访问)
223 |
224 | ```go
225 | var In int // In is exported
226 | var in byte // in is unexported
227 | var ȸȹ string // ȸȹ is unexported
228 | const Ȼom bool = false // Ȼom is exported
229 | const ѧѩ uint8 = 1 // ѧѩ is unexported
230 | type Ĩnteger int // Ĩnteger is exported
231 | type ブーリアン *bool // ブーリアン is unexported
232 | func Ӭxport() {...} // Ӭxport is exported
233 | func įnner() {...} // įnner is unexported
234 | func (me *Integer) ⱱalueOf(s string) int {...} // ⱱalueOf is unexported
235 | func (i ブーリアン) Ȿtring() string {...} // Ȿtring is exported
236 | ```
237 |
238 | - internal包(内部包) `Go1.4+`
239 | internal包及其子包中的导出元素只能被与internal同父包的其他包访问
240 |
241 | 例如有Go目录如下:
242 | `MODULE`
243 | ├─x0
244 | │ ├─`internal`
245 | │ │ └─z0
246 | │ └─y0
247 | │ └─z1
248 | └─x1
249 | └─y1
250 |
251 | 在x0,y0,z1包中可以访问internal,z0包中的可见元素
252 | 在x1,y1包中不能导入internal,z0包
253 |
254 | - 规范导入包路径Canonical import paths `Go1.4+`
255 | 包声明语句后面添加标记注释,用于标识这个包的规范导入路径。
256 |
257 | ```go
258 | package pdf // import "rsc.io/pdf"
259 | ```
260 |
261 | 如果使用此包的代码的导入的路径不是规范路径,go命令会拒绝编译。
262 | 例如有 [rsc.io/pdf]() 的一个fork路径 [github.com/rsc/pdf]()
263 | 如下程序代码导入路径时使用了非规范的路径则会被go拒绝编译
264 |
265 | ```go
266 | import "github.com/rsc/pdf"
267 | ```
268 |
269 | ## **数据类型 Data Type**
270 |
271 | ### **基础数据类型 Basic data type**
272 |
273 | - 基本类型包含:数值类型,布尔类型,字符串
274 |
275 | | 类型 | 占用空间(byte) | 取值范围 | 默认零值 | 类型 | 占用空间(byte) | 取值范围 | 默认零值 |
276 | |:---------------|:----------:|:--------------------------------------:|:------:|:--------------:|:----------:|:------------------------:|:-------:|
277 | | `int` | 4,8 | **int32,int64** | 0 | `uint` | 4,8 | **uint32,uint64** | 0 |
278 | | `int8` | 1 | **-27 ~ 27-1** | 0 | `uint8`,`byte` | 1 | 0 ~ **28-1** | 0 |
279 | | `int16` | 2 | **-215 ~ 215-1** | 0 | `uint16` | 2 | 0 ~ **216-1** | 0 |
280 | | `int32`,`rune` | 4 | **-231 ~ 231-1** | 0 | `uint32` | 4 | 0 ~ **232-1** | 0 |
281 | | `int64` | 8 | **-263 ~ 263-1** | 0 | `uint64` | 8 | 0 ~ **264-1** | 0 |
282 | | `float32` | 4 | IEEE-754 32-bit | 0.0 | `float64` | 4 | IEEE-754 64-bit | 0.0 |
283 | | `complex64` | 8 | **float32+float32**i | 0 + 0i | `complex128` | 16 | **float64+float64**i | 0 + 0i |
284 | | `bool` | 1 | **true,false** | false | `string` | - | "" ~ "∞" | "",\`\` |
285 | | `uintptr` | 4,8 | **uint32,uint64** | 0 | `error` | - | - | nil |
286 |
287 | > `byte` 是 `uint8` 的别名
288 | `rune` 是 `int32` 的别名,代表一个Unicode码点
289 | `int`与`int32`或`int64`是不同的类型,只是根据架构对应32/64位值
290 | `uint`与`uint32`或`uint64`是不同的类型,只是根据架构对应32/64位值
291 |
292 | ### **变量 Variable, keyword:`var`**
293 |
294 | - 变量声明, 使用`var`关键字
295 | Go中只能使用`var` **声明**变量,无需显式初始化值
296 |
297 | ```go
298 | var i int // i = 0
299 |
300 | var s string // s = "" (Go中的string是值类型,默认零值是空串 "" 或 ``,不存在nil(null)值)
301 |
302 | var e error // e = nil, error是Go的内建接口类型。
303 | ```
304 |
305 | 关键字的顺序错误或缺少都是编译错误的
306 |
307 | ```go
308 | var int a // ❌编译错误
309 | a int // ❌编译错误
310 | int a // ❌编译错误
311 | ```
312 |
313 | - `var` 语句可以声明一个变量列表,类型在变量名之后
314 |
315 | ```go
316 | var a,b,c int // a = 0, b = 0, c = 0
317 | var (
318 | a int // a = 0
319 | b string // b = ""
320 | c uint // c = 0
321 | )
322 | var (
323 | a,b,c int
324 | d string
325 | )
326 | ```
327 |
328 | - 变量定义时初始化赋值,每个变量对应一个值
329 |
330 | ```go
331 | var a int = 0
332 | var a, b int = 0, 1
333 | ```
334 |
335 | - 变量定义并初始化时可以省略类型,Go自动根据初始值推导变量的类型
336 |
337 | ```go
338 | var a = 'A' // a int32
339 | var a,b = 0, "B" // a int, b string
340 | ```
341 |
342 | - 使用组合符号`:=`定义并初始化变量,根据符号右边表达式的值的类型声明变量并初始化它的值
343 | `:=` 不能在函数外使用,函数外的每个语法块都必须以关键字开始
344 |
345 | ```go
346 | a := 3 // a int
347 | a, b, c := 8, '呴', true // a int, b int32, c bool
348 | c := `formatted
349 | string` // c string
350 | c := 1 + 2i // c complex128
351 | ```
352 |
353 | ### **常量 Constant, keyword:`const`**
354 |
355 | - 常量可以是字符、字符串、布尔或数值类型的值,数值常量是高精度的值
356 |
357 | ```go
358 | const x int = 3
359 | const y,z int = 1,2
360 | const (
361 | a byte = 'A'
362 | b string = "B"
363 | c bool = true
364 | d int = 4
365 | e float32 = 5.1
366 | f complex64 = 6 + 6i
367 | )
368 | ```
369 |
370 | - 根据常量值自动推导类型
371 |
372 | ```go
373 | const a = 0 // a int
374 | const (
375 | b = 2.3 // b float64
376 | c = true // c bool
377 | )
378 | ```
379 |
380 | - 常量组内定义时复用表达式
381 | 常量组内定义的常量只有名称时,其值会根据上一次最后出现的常量表达式计算相同的类型与值
382 |
383 | ```go
384 | const (
385 | a = 3 // a = 3
386 | b // b = 3
387 | c // c = 3
388 | d = len("asdf") // d = 4
389 | e // e = 4
390 | f // f = 4
391 | g,h,i = 7,8,9 // 复用表达式要一一对应
392 | x,y,z // x = 7, y = 8, z = 9
393 | )
394 | ```
395 |
396 | - 自动递增枚举常量 `iota`
397 | iota的枚举值可以赋值给数值兼容类型
398 | 每个常量单独声明时,`iota`不会自动递增
399 |
400 | ```go
401 | const a int = iota // a = 0
402 | const b int = iota // b = 0
403 | const c byte = iota // c = 0
404 | const d uint64 = iota // d = 0
405 | ```
406 |
407 | - 常量组合声明时,`iota`每次引用会逐步自增,初始值为0,步进值为1
408 |
409 | ```go
410 | const (
411 | a uint8 = iota // a = 0
412 | b int16 = iota // b = 1
413 | c rune = iota // c = 2
414 | d float64 = iota // d = 3
415 | e uintptr = iota // e = 4
416 | )
417 | ```
418 |
419 | - 即使`iota`不是在常量组内第一个开始引用,也会按组内常量数量递增
420 |
421 | ```go
422 | const (
423 | a = "A"
424 | b = 'B'
425 | c = iota // c = 2
426 | d = "D"
427 | e = iota // e = 4
428 | )
429 | ```
430 |
431 | - 枚举的常量都为同一类型时,可以使用简单序列格式(组内复用表达式).
432 |
433 | ```go
434 | const (
435 | a = iota // a int32 = 0
436 | b // b int32 = 1
437 | c // c int32 = 2
438 | )
439 | ```
440 |
441 | - 枚举序列中的未指定类型的常量会跟随序列前面最后一次出现类型定义的类型
442 |
443 | ```go
444 | const (
445 | a byte = iota // a uint8 = 0
446 | b // b uint8 = 1
447 | c // c uint8 = 2
448 | d rune = iota // d int32 = 3
449 | e // e int32 = 4
450 | f // f int32 = 5
451 | )
452 | ```
453 |
454 | - `iota`自增值只在一个常量定义组合中有效,跳出常量组合定义后`iota`初始值归0
455 |
456 | ```go
457 | const (
458 | a = iota // a int32 = 0
459 | b // b int32 = 1
460 | c // c int32 = 2
461 | )
462 | const (
463 | e = iota // e int32 = 0 (iota重新初始化并自增)
464 | f // f int32 = 1
465 | )
466 | ```
467 |
468 | - 定制`iota`序列初始值与步进值 (通过组合内复用表达式实现)
469 |
470 | ```go
471 | const (
472 | a = (iota + 2) * 3 // a int32 = 6 (a=(0+2)*3) 初始值为6,步进值为3
473 | b // b int32 = 9 (b=(1+2)*3)
474 | c // c int32 = 12 (c=(2+2)*3)
475 | d // d int32 = 15 (d=(3+2)*3)
476 | )
477 | ```
478 |
479 | ### **数组 Array**
480 |
481 | - 数组声明带有长度信息且长度固定,数组是值类型默认零值不是`nil`,传递参数时会进行复制。
482 | 声明定义数组时中括号`[ ]`在类型名称之前,赋值引用元素时中括号`[ ]`在数组变量名之后。
483 |
484 | ```go
485 | var a [3]int = [3]int{0, 1, 2} // a = [0 1 2]
486 | var b [3]int = [3]int{} // b = [0 0 0]
487 | var c [3]int
488 | c = [3]int{}
489 | c = [3]int{0,0,0} // c = [0 0 0]
490 | d := [3]int{} // d = [0 0 0]
491 | fmt.Printf("%T\t%#v\t%d\t%d\n", d, d, len(d), cap(d)) // [3]int [3]int{0, 0, 0} 3 3
492 | ```
493 |
494 | 使用`...`自动计算数组的长度
495 |
496 | ```go
497 | var a = [...]int{0, 1, 2}
498 |
499 | // 多维数组只能自动计算最外围数组长度
500 | x := [...][3]int{{0, 1, 2}, {3, 4, 5}}
501 | y := [...][2][2]int{{{0,1},{2,3}},{{4,5},{6,7}}}
502 |
503 | // 通过下标访问数组元素
504 | println(y[1][1][0]) // 6
505 | ```
506 |
507 | 初始化指定索引的数组元素,未指定初始化的元素保持默认零值
508 |
509 | ```go
510 | var a = [3]int{2:3}
511 | var b = [...]string{2:"c", 3:"d"}
512 | ```
513 |
514 | ### **切片 Slice**
515 |
516 | - slice 切片是对一个数组上的连续一段的引用,并且同时包含了长度和容量信息
517 | 因为是引用类型,所以未初始化时的默认零值是`nil`,长度与容量都是0
518 |
519 | ```go
520 | var a []int
521 | fmt.Printf("%T\t%#v\t%d\t%d\n", a, a, len(a), cap(a)) // []int []int(nil) 0 0
522 |
523 | // 可用类似数组的方式初始化slice
524 | var d []int = []int{0, 1, 2}
525 | fmt.Printf("%T\t%#v\t%d\t%d\n", d, d, len(d), cap(d)) // []int []int{0, 1, 2} 3 3
526 |
527 | var e = []string{2:"c", 3:"d"}
528 | ```
529 |
530 | 使用内置函数make初始化slice,第一参数是slice类型,第二参数是长度,第三参数是容量(省略时与长度相同)
531 |
532 | ```go
533 | var b = make([]int, 0)
534 | fmt.Printf("%T\t%#v\t%d\t%d\n", b, b, len(b), cap(b)) // []int []int{} 0 0
535 |
536 | var c = make([]int, 3, 10)
537 | fmt.Printf("%T\t%#v\t%d\t%d\n", c, c, len(c), cap(c)) // []int []int{} 3 10
538 |
539 | var a = new([]int)
540 | fmt.Printf("%T\t%#v\t%d\t%d\n", a, a, len(*a), cap(*a)) // *[]int &[]int(nil) 0 0
541 | ```
542 |
543 | - 基于slice或数组重新切片,创建一个新的 slice 值指向相同的数组
544 | 重新切片支持两种格式:
545 |
546 | 2个参数 `slice[beginIndex:endIndex]`
547 | 需要满足条件:0 <= beginIndex <= endIndex <= cap(slice)
548 | 截取从开始索引到结束索引-1 之间的片段
549 | 新slice的长度:`length=(endIndex - beginIndex)`
550 | 新slice的容量:`capacity=(cap(slice) - beginIndex)`
551 | beginIndex的值可省略,默认为0
552 | endIndex 的值可省略,默认为len(slice)
553 |
554 | ```go
555 | s := []int{0, 1, 2, 3, 4}
556 | a := s[1:3] // a: [1 2], len: 2, cap: 4
557 | b := s[:4] // b: [0 1 2 3], len: 4, cap: 5
558 | c := s[1:] // c: [1 2 3 4], len: 4, cap: 4
559 | d := s[1:1] // d: [], len: 0, cap: 4
560 | e := s[:] // e: [0 1 2 3 4], len: 5, cap: 5
561 | ```
562 |
563 | 3个参数 `slice[beginIndex:endIndex:capIndex]` `Go1.2+`
564 | 需要满足条件:0 <= beginIndex <= endIndex <= capIndex <= cap(slice)
565 | 新slice的长度:`length=(endIndex - beginIndex)`
566 | 新slice的容量:`capacity=(capIndex - beginIndex)`
567 | beginIndex的值可省略,默认为0
568 |
569 | ```go
570 | s := make([]int, 5, 10)
571 | a := s[9:10:10] // a: [0], len: 1, cap: 1
572 | b := s[:3:5] // b: [0 0 0], len: 3, cap: 5
573 | ```
574 |
575 | - 切片类型转换为数组类型
576 | 数组是值类型通过切片强制转换类型得到的是从原切片指向底层数组的副本
577 | 转换的数组类型可以指定长度为小于/等于原切片的长度值 (不是切片的容量值)
578 |
579 | Go1.19及之前的版本需要先转换为数组指针后再从指针取值
580 |
581 | ```go
582 | slice := []int{1,2,3,4,5}
583 | array := *(*[3]int)(slice)
584 | ```
585 |
586 | 直观的方式直接强制转换类型 `Go1.20+`
587 |
588 | ```go
589 | slice := []int{1,2,3}
590 | array := [3]int(slice)
591 | ```
592 |
593 | ```go
594 | slice := make([]int, 0, 10)
595 | array0 := [0]int(slice) // 此时切片的长度是0
596 | array1 := [3]int(slice) // panic: 不能将切片0长度的底层数组转换为指定长度的数组
597 | slice = append(slice, 1,2,3,4,5) // 此时切片的长度是5
598 | arrPtr := (*[5]int)(slice) // 获取切片底层数组的指针
599 | array := [3]int(slice) // 强制转换类型从切片底层数组复制前3个元素构造了新的数组值
600 |
601 | arrPtr[1] = 20 // 切片底层数组的指针修改数组的值会影响原切片
602 | array[2] = 30 // 新数组的值不再影响原切片
603 | fmt.Println(slice) // [1 20 3 4 5]
604 | fmt.Println(array) // [1 2 30]
605 | ```
606 |
607 | - 向slice中追加/修改元素
608 |
609 | ```go
610 | s := []string{}
611 | s = append(s, "a") // 添加一个元素
612 | s = append(s, "b", "c", "d") // 添加一列元素
613 | t = []string{"e", "f", "g"}
614 | s = append(s, t...} // 添加另一个切片t的所有元素
615 | s = append(s, t[:2]...} // 添加另一个切片t的部分元素
616 |
617 | s[0] = "A" // 修改切片s的第一个元素
618 | s[len(s)-1] = "G" // 修改切片s的最后一个元素
619 | ```
620 |
621 | - 向slice指定位置插入元素,从slice中删除指定的元素
622 | 因为slice引用指向底层数组,数组的长度不变元素是不能插入/删除的
623 | 插入的原理就是从插入的位置将切片分为两部分依次将首部、新元素、尾部拼接为一个新的切片
624 | 删除的原理就是排除待删除元素后用其他元素重新构造一个数组
625 |
626 | ```go
627 | func insertSlice(s []int, i int, elements ...int) []int {
628 | // x := append(s[:i], append(elements, s[i:]...)...)
629 | x := s[:i]
630 | x = append(x, elements...)
631 | x = append(x, s[i:]...)
632 | return x
633 | }
634 |
635 | func deleteByAppend() {
636 | i := 3
637 | s := []int{1, 2, 3, 4, 5, 6, 7}
638 | // delete the fourth element(index is 3), using append
639 | s = append(s[:i], s[i+1:]...)
640 | }
641 |
642 | func deleteByCopy() {
643 | i := 3
644 | s := []int{1, 2, 3, 4, 5, 6, 7}
645 | // delete the fourth element(index is 3), using copy
646 | copy(s[i:], s[i+1:])
647 | s = s[:len(s)-1]
648 | }
649 | ```
650 |
651 | ### **字典/映射 Map, keyword:`map`**
652 |
653 | - map是引用类型,使用内置函数 `make`进行初始化,未初始化的map零值为 `nil`长度为0,并且不能赋值元素
654 |
655 | ```go
656 | var m map[int]int
657 | m[0] = 0 // × runtime error: assignment to entry in nil map
658 | fmt.Printf("type: %T\n", m) // map[int]int
659 | fmt.Printf("value: %#v\n", m) // map[int]int(nil)
660 | fmt.Printf("value: %v\n", m) // map[]
661 | fmt.Println("is nil: ", nil == m) // true
662 | fmt.Println("length: ", len(m)) // 0,if m is nil, len(m) is zero.
663 | ```
664 |
665 | 使用内置函数make初始化map
666 |
667 | ```go
668 | var m map[int]int = make(map[int]int)
669 | m[0] = 0 // 插入或修改元素
670 | fmt.Printf("type: %T\n", m) // map[int]int
671 | fmt.Printf("value: %#v\n", m) // map[int]int(0:0)
672 | fmt.Printf("value: %v\n", m) // map[0:0]
673 | fmt.Println("is nil: ", nil == m) // false
674 | fmt.Println("length: ", len(m)) // 1
675 | ```
676 |
677 | 直接赋值初始化map
678 |
679 | ```go
680 | m := map[int]int{
681 | 0:0,
682 | 1:1, // 最后的逗号是必须的
683 | }
684 | n := map[string]S{
685 | "a":S{0,1},
686 | "b":{2,3}, // 类型名称可省略
687 | }
688 | ```
689 |
690 | map的使用:读取、添加、修改、删除元素
691 |
692 | ```go
693 | m[0] = 3 // 修改m中key为0的值为3
694 | m[4] = 8 // 添加到m中key为4值为8
695 |
696 | a := n["a"] // 获取n中key为“a“的值
697 | b, ok := n["c"] // 取值, 并通过ok(bool)判断key对应的元素是否存在.
698 |
699 | delete(n, "a") // 使用内置函数delete删除key为”a“对应的元素.
700 | ```
701 |
702 | ### **结构体 Struct, keyword:`type` `struct`**
703 |
704 | - 结构体类型`struct`是一个字段的集合
705 |
706 | ```go
707 | type S struct {
708 | A int
709 | B, c string
710 | }
711 | ```
712 |
713 | - 结构体初始化通过结构体字段的值作为列表来新分配一个结构体。
714 |
715 | ```go
716 | var s S = S{0, "1", "2"}
717 | ```
718 |
719 | - 使用 Name: 语法可以仅列出部分字段(字段名的顺序无关)
720 |
721 | ```go
722 | var s S = S{B: "1", A: 0}
723 | ```
724 |
725 | - 结构体是值类型,传递时会复制值,其默认零值不是`nil`
726 |
727 | ```go
728 | var a S
729 | var b = S{}
730 | fmt.Println(a == b) // true
731 | ```
732 |
733 | - 结构体组合
734 | 将一个`命名类型`作为匿名字段嵌入一个结构体
735 | 嵌入匿名字段支持命名类型、命名类型的指针和接口类型
736 |
737 | ```go
738 | package main
739 |
740 | type (
741 | A struct {
742 | v int
743 | }
744 |
745 | // B 定义结构体B,嵌入结构体A作为匿名字段
746 | B struct {
747 | A
748 | }
749 |
750 | // C 定义结构体C,嵌入结构体A的指针作为匿名字段
751 | C struct {
752 | *A
753 | }
754 | )
755 |
756 | func (a *A) setV(v int) {
757 | a.v = v
758 | }
759 |
760 | func (a A) getV() int {
761 | return a.v
762 | }
763 |
764 | func (b B) getV() string {
765 | return "B"
766 | }
767 |
768 | func (c *C) getV() bool {
769 | return true
770 | }
771 |
772 | func main() {
773 | a := A{}
774 | b := B{} // 初始化结构体B,其内匿名字段A默认零值是A{}
775 | c := C{&A{}} // 初始化结构体C,其内匿名指针字段*A默认零值是nil,需要初始化赋值
776 |
777 | println(a.v)
778 |
779 | // 结构体A嵌入B,A内字段自动提升到B
780 | println(b.v)
781 |
782 | // 结构体指针*A嵌入C,*A对应结构体内字段自动提升到C
783 | println(c.v)
784 |
785 | a.setV(3)
786 | b.setV(5)
787 | c.setV(7)
788 | println(a.getV(), b.A.getV(), c.A.getV())
789 | println(a.getV(), b.getV(), c.getV())
790 | }
791 | ```
792 |
793 | - 匿名结构体
794 | 匿名结构体声明时省略了`type`关键字,并且没有名称
795 |
796 | ```go
797 | package main
798 |
799 | import "fmt"
800 |
801 | type Integer int
802 |
803 | // 声明变量a为空的匿名结构体类型
804 | var a struct{}
805 |
806 | // 声明变量b为包含一个字段的匿名结构体类型
807 | var b struct{ x int }
808 |
809 | // 声明变量c为包含两个字段的匿名结构体类型
810 | var c struct {
811 | u int
812 | v bool
813 | }
814 |
815 | func main() {
816 | printa(a)
817 | b.x = 1
818 | fmt.Printf("bx: %#v\n", printb(b)) // bx: struct { y uint8 }{y:0x19}
819 | printc(c)
820 |
821 | // 声明d为包含3个字段的匿名结构体并初始化部分字段
822 | d := struct {
823 | x int
824 | y complex64
825 | z string
826 | }{
827 | z: "asdf",
828 | x: 111,
829 | }
830 | d.y = 22 + 333i
831 | fmt.Printf("d: %#v\n", d) // d: struct { x int; y complex64; z string }{x:111, y:(22+333i), z:"asdf"}
832 |
833 | // 声明变量e为包含两个字段的匿名结构体类型
834 | // 包含1个匿名结构体类型的命名字段和1个命名类型的匿名字段
835 | e := struct {
836 | a struct{ x int }
837 | // 结构体组合嵌入匿名字段只支持命名类型
838 | Integer
839 | }{}
840 | e.Integer = 444
841 | fmt.Printf("e: %#v\n", e) // e: struct { a struct { x int }; main.Integer }{a:struct { x int }{x:0}, Integer:444}
842 | }
843 |
844 | // 函数参数为匿名结构体类型时,传入参数类型声明必须保持一致
845 | func printa(s struct{}) {
846 | fmt.Printf("a: %#v\n", s) // a: struct {}{}
847 | }
848 |
849 | // 函数入参和返回值都支持匿名结构体类型
850 | func printb(s struct{ x int }) (x struct{ y byte }) {
851 | fmt.Printf("b: %#v\n", s) // b: struct { x int }{x:1}
852 | x.y = 25
853 | return
854 | }
855 |
856 | func printc(s struct {u int; v bool }) {
857 | fmt.Printf("c: %#v\n", s) // c: struct { u int; v bool }{u:0, v:false}
858 | }
859 | ```
860 |
861 | ### **指针 Pointer**
862 |
863 | - 通过取地址操作符`&`获取指向值/引用对象的指针。
864 |
865 | ```go
866 | var i int = 1
867 | pi := &i // 指向数值的指针
868 |
869 | a := []int{0, 1, 2}
870 | pa := &a // 指向引用对象的指针
871 |
872 | var s *S = &S{0, "1", "2"} // 指向值对象的指针
873 | ```
874 |
875 | - 内置函数`new(T)`分配了一个零初始化的 T 值,并返回指向它的指针
876 |
877 | ```go
878 | var i = new(int)
879 | var s *S = new(S)
880 | ```
881 |
882 | - 使用`*`读取/修改指针指向的值
883 |
884 | ```go
885 | func main() {
886 | i := new(int)
887 | *i = 3
888 | println(i, *i) // 0xc208031f80 3
889 |
890 | i = new(int)
891 | println(i, *i) // 0xc208031f78 0
892 | }
893 | ```
894 |
895 | - 指针使用点号来访问结构体字段
896 | 结构体字段/方法可以通过结构体指针来访问,通过指针间接的访问是透明的。
897 |
898 | ```go
899 | fmt.Println(s.A)
900 | fmt.Println((*s).A)
901 | ```
902 |
903 | - 指针的指针
904 |
905 | ```go
906 | func main() {
907 | var i int
908 | var p *int
909 | var pp **int
910 | var ppp ***int
911 | var pppp ****int
912 | println(i, p, pp, ppp, pppp) // 0 0x0 0x0 0x0 0x0
913 |
914 | i, p, pp, ppp, pppp = 123, &i, &p, &pp, &ppp
915 | println(i, p, pp, ppp, pppp) // 123 0xc208031f68 0xc208031f88 0xc208031f80 0xc208031f78
916 | println(i, *p, **pp, ***ppp, ****pppp) // 123 123 123 123 123
917 | }
918 | ```
919 |
920 | - 跨层指针元素的使用
921 | 在指针引用多层对象时,指针是针对引用表达式的最后一位元素。
922 |
923 | ```go
924 | package a
925 |
926 | type X struct {
927 | A Y
928 | }
929 | type Y struct {
930 | B Z
931 | }
932 | type Z struct {
933 | C int
934 | }
935 | ```
936 |
937 | ```go
938 | package main
939 | import (
940 | "a"
941 | "fmt"
942 | )
943 |
944 | func main() {
945 | var x = a.X{}
946 | var p = &x
947 | fmt.Println("x: ", x) // x: {{{0}}}
948 | println("p: ", p) // p: 0xc208055f20
949 | fmt.Println("*p: ", *p) // *p: {{{0}}}
950 | println("x.A.B.C: ", x.A.B.C) // x.A.B.C: 0
951 | // println("*p.A.B.C: ", *p.A.B.C) // invalid indirect of p.A.B.C (type int)
952 | println("(*p).A.B.C: ", (*p).A.B.C) // (*p).A.B.C: 0
953 | }
954 | ```
955 |
956 | - Go的指针没有指针运算,但是 **道高一尺,魔高一丈**
957 | [Go语言中的指针运算](http://1234n.com/?post/rseosp)
958 | [利用unsafe操作未导出变量](http://my.oschina.net/goal/blog/193698)
959 |
960 | ### **通道 Channel, keyword:`chan`**
961 |
962 | - channel用于两个goroutine之间传递指定类型的值来同步运行和通讯。
963 | 操作符`<-`用于指定channel的方向,发送或接收。
964 | 如果未指定方向,则为双向channel。
965 |
966 | ```go
967 | var c0 chan int // 可用来发送和接收int类型的值
968 | var c1 chan<- int // 可用来发送int类型的值
969 | var c2 <-chan int // 可用来接收int类型的值
970 | ```
971 |
972 | - channel是引用类型,使用`make`函数来初始化。
973 | 未初始化的channel零值是`nil`,且不能用于发送和接收值。
974 |
975 | ```go
976 | c0 := make(chan int) // 不带缓冲的int类型channel
977 | c1 := make(chan *int, 10) // 带缓冲的*int类型指针channel
978 | ```
979 | 无缓冲的channel没有接收者时发送方会阻塞,直到有接收方从channel中取出值。
980 | 带缓冲的channel在缓冲区已满时发送方会阻塞,直到接收方从channel中取出值。
981 | 接收方在channel中无值会一直阻塞。
982 |
983 | - 通过channel发送一个值时,`<-`作为二元操作符使用,
984 |
985 | ```go
986 | c0 <- 3
987 | ```
988 |
989 | 通过channel接收一个值时,`<-`作为一元操作符使用。
990 |
991 | ```go
992 | i := <-c1
993 | ```
994 |
995 | - 关闭channel,只能用于双向或只发送类型的channel
996 | 只能由 **发送方**调用`close`函数来关闭channel
997 | 接收方取出已关闭的channel中发送的值后,后续再从channel中取值时会以非阻塞的方式立即返回channel传递类型的零值。
998 |
999 | ```go
1000 | ch := make(chan string, 1)
1001 |
1002 | // 发送方,发送值后关闭channel
1003 | ch <- "hello"
1004 | close(ch)
1005 |
1006 |
1007 | // 接收方,取出发送的值
1008 | fmt.Println(<-ch) // 输出: “hello”
1009 |
1010 | // 再次从已关闭的channel中取值,返回channel传递类型的零值
1011 | fmt.Println(<-ch) // 输出: 零值,空字符串“”
1012 |
1013 | // 接收方判断接收到的零值是由发送方发送的还是关闭channel返回的默认值
1014 | s, ok := <-ch
1015 | if ok {
1016 | fmt.Println("Receive value from sender:", s)
1017 | } else {
1018 | fmt.Println("Get zero value from closed channel")
1019 | }
1020 |
1021 | // 向已关闭的通道发送值会产生运行时恐慌panic
1022 | ch <- "hi"
1023 | // 再次关闭已经关闭的通道也会产生运行时恐慌panic
1024 | close(ch)
1025 | ```
1026 |
1027 | - 使用`for range`语句依次读取发送到channel的值,直到channel关闭。
1028 |
1029 | ```go
1030 | package main
1031 |
1032 | import "fmt"
1033 |
1034 | func main() {
1035 | // 无缓冲和有缓冲的channel的range用法相同
1036 | var ch = make(chan int) // make(chan int, 2) 或 make(chan int , 100)
1037 | go func() {
1038 | for i := 0; i < 5; i++ {
1039 | ch <- i
1040 | }
1041 | close(ch)
1042 | }()
1043 |
1044 | // channel中无发送值且未关闭时会阻塞
1045 | for x := range ch {
1046 | fmt.Println(x)
1047 | }
1048 | }
1049 | ```
1050 |
1051 | 下面方式与for range用法效果相同
1052 |
1053 | ```go
1054 | loop:
1055 | for {
1056 | select {
1057 | case x, ok := <-c:
1058 | if !ok {
1059 | break loop
1060 | }
1061 | fmt.Println(x)
1062 | }
1063 | }
1064 | ```
1065 |
1066 | ### **接口 Interface, keyword:`interface`**
1067 |
1068 | - 接口类型是由一组类型定义的集合。
1069 | 接口类型的值可以存放实现这些方法的任何值。
1070 |
1071 | ```go
1072 | type Abser interface {
1073 | Abs() float64
1074 | }
1075 | ```
1076 |
1077 | - 类型通过实现定义的方法来实现接口, 不需要显式声明实现某接口。
1078 |
1079 | ```go
1080 | type MyFloat float64
1081 |
1082 | func (f MyFloat) Abs() float64 {
1083 | if f < 0 {
1084 | return float64(-f)
1085 | }
1086 | return float64(f)
1087 | }
1088 | ```
1089 |
1090 | - 接口组合
1091 |
1092 | ```go
1093 | type Reader interface {
1094 | Read(b []byte) (n int)
1095 | }
1096 |
1097 | type Writer interface {
1098 | Write(b []byte) (n int)
1099 | }
1100 |
1101 | // 接口ReadWriter组合了Reader和Writer两个接口
1102 | type ReadWriter interface {
1103 | Reader
1104 | Writer
1105 | }
1106 |
1107 | type File struct {
1108 | // ...
1109 | }
1110 |
1111 | func (f *File) Read(b []byte) (n int) {
1112 | println("Read", len(b),"bytes data.")
1113 | return len(b)
1114 | }
1115 |
1116 | func (f *File) Write(b []byte) (n int) {
1117 | println("Write", len(b),"bytes data.")
1118 | return len(b)
1119 | }
1120 |
1121 | func main() {
1122 | // *File 实现了Read方法和Write方法,所以实现了Reader接口和Writer接口以及组合接口ReadWriter
1123 | var f *File = &File{}
1124 | var r Reader = f
1125 | var w Writer = f
1126 | var rw ReadWriter = f
1127 | bs := []byte("asdf")
1128 | r.Read(bs)
1129 | rw.Read(bs)
1130 | w.Write(bs)
1131 | rw.Write(bs)
1132 | }
1133 | ```
1134 |
1135 | - 内置接口类型`error`是一个用于表示错误情况的常规接口,其零值`nil`表示没有错误
1136 | 所有实现了`Error`方法的类型都能表示为一个错误
1137 |
1138 | ```go
1139 | type error interface {
1140 | Error() string
1141 | }
1142 | ```
1143 |
1144 | ### **自定义类型**
1145 |
1146 | - Go中支持自定义的类型可基于: 基本类型、数组类型、切片类型、字典类型、函数类型、结构体类型、通道类型、接口类型以及自定义类型的类型
1147 |
1148 | ```go
1149 | type (
1150 | A int
1151 | B int8
1152 | C int16
1153 | D rune
1154 | E int32
1155 | F int64
1156 | G uint
1157 | H byte
1158 | I uint16
1159 | J uint32
1160 | K uint64
1161 | L float32
1162 | M float64
1163 | N complex64
1164 | O complex128
1165 | P uintptr
1166 | Q bool
1167 | R string
1168 | S [3]uint8
1169 | T []complex128
1170 | U map[string]uintptr
1171 | V func(i int) (b bool)
1172 | W struct {a, b int}
1173 | X chan int
1174 | Y any
1175 | Z A
1176 | )
1177 | ```
1178 |
1179 | - 以及支持以上所有支持类型的指针类型
1180 |
1181 | ```go
1182 | type (
1183 | A *int
1184 | B *int8
1185 | C *int16
1186 | D *rune
1187 | E *int32
1188 | F *int64
1189 | G *uint
1190 | H *byte
1191 | I *uint16
1192 | J *uint32
1193 | K *uint64
1194 | L *float32
1195 | M *float64
1196 | N *complex64
1197 | O *complex128
1198 | P *uintptr
1199 | Q *bool
1200 | R *string
1201 | S *[3]uint8
1202 | T *[]complex128
1203 | U *map[string]uintptr
1204 | V *func(i int) (b bool)
1205 | W *struct {a, b int}
1206 | X *chan int
1207 | Y *any
1208 | Z *A
1209 | )
1210 | ```
1211 |
1212 | - 类型别名 `Go1.9+`
1213 |
1214 | ```go
1215 | type (
1216 | A struct{}
1217 | B struct{} // 定义两个结构相同的类型A,B
1218 | C = A // 定义类型A的别名
1219 | )
1220 |
1221 | func main() {
1222 | var (
1223 | a A
1224 | b B
1225 | c C
1226 | )
1227 | // 因为类型名不同,所以a和b不是相同类型,此处编译错误
1228 | fmt.Println(a == b) // ❌invalid operation: a == b (mismatched types A and B)
1229 |
1230 | fmt.Println(a == c) // true
1231 | a = C{}
1232 | c = A{}
1233 | fmt.Println(c == a) // true
1234 | }
1235 | ```
1236 |
1237 | - 强制类型转换
1238 |
1239 | 数据类型转换语法规则
1240 |
1241 | ```go
1242 | // T 为新的数据类型
1243 | newDataTypeVariable = T(oldDataTypeVariable)
1244 | ```
1245 |
1246 | 数值类型转换
1247 |
1248 | ```go
1249 | var i int = 123
1250 | var f = float64(i)
1251 | var u = uint(f)
1252 | ```
1253 |
1254 | 接口类型转换
1255 | 任意类型的数据都可以转换为其类型已实现的接口类型
1256 |
1257 | ```go
1258 | type I any
1259 |
1260 | var x int = 123
1261 | var y = I(x)
1262 |
1263 | var s struct{a string}
1264 | var t = I(s)
1265 | ```
1266 |
1267 | 结构体类型转换 `Go1.8+`
1268 | 如果两个结构体包含的所有字段的名称和类型相同(忽略字段的标签差异),则可以互相强制转换类型。
1269 |
1270 | ```go
1271 | type T1 struct {
1272 | X int `json:"foo"`
1273 | }
1274 |
1275 | type T2 struct {
1276 | X int `json:"bar"`
1277 | }
1278 |
1279 | var v1 = T1{X: 123}
1280 | var v2 = T2(v1)
1281 | ```
1282 |
1283 | ## **语句 Statement**
1284 |
1285 | ### **分号/括号 ; {**
1286 |
1287 | - Go是采用语法解析器自动在每行末尾增加分号,所以在写代码的时候可以省略分号。
1288 |
1289 | - Go编程中只有几个地方需要手工增加分号:
1290 | for循环使用分号把初始化、条件和遍历元素分开。
1291 | if/switch的条件判断带有初始化语句时使用分号分开初始化语句与判断语句。
1292 | 在一行中有多条语句时,需要增加分号。
1293 |
1294 | - 控制语句(if,for,switch,select)、函数、方法 的左大括号不能单独放在一行, 语法解析器会在大括号之前自动插入一个分号,导致编译错误。
1295 |
1296 | ### **条件语句 if, keyword:`if` `else`**
1297 |
1298 | - `if`语句 小括号 ( )是可选的,而大括号 { } 是必须的。
1299 |
1300 | ```go
1301 | if (i < 0) // ❌编译错误.
1302 | println(i)
1303 |
1304 | if i < 0 // ❌编译错误.
1305 | println(i)
1306 |
1307 | if (i < 0) { // 编译通过.
1308 | println(i)
1309 | }
1310 |
1311 | if (i < 0 || i > 10) {
1312 | println(i)
1313 | }
1314 |
1315 | if i < 0 {
1316 | println(i)
1317 | } else if i > 5 && i <= 10 {
1318 | println(i)
1319 | } else {
1320 | println(i)
1321 | }
1322 | ```
1323 |
1324 | - 可以在条件之前执行一个简单的语句,由这个语句定义的变量的作用域仅在 if / else if / else 范围之内
1325 |
1326 | ```go
1327 | if (i := 0; i < 1) { // ❌编译错误.
1328 | println(i)
1329 | }
1330 |
1331 | if i := 0; (i < 1) { // 编译通过.
1332 | println(i)
1333 | }
1334 |
1335 | if i := 0; i < 0 { // 使用gofmt格式化代码会自动移除代码中不必要的小括号( )
1336 | println(i)
1337 | } else if i == 0 {
1338 | println(i)
1339 | } else {
1340 | println(i)
1341 | }
1342 | ```
1343 |
1344 | - `if`语句作用域范围内定义的变量会覆盖外部同名变量,与方法函数内局部变量覆盖全局变量同理
1345 |
1346 | ```go
1347 | a, b := 0, 1
1348 | if a, b := 3, 4; a > 1 && b > 2 {
1349 | println(a, b) // 3 4
1350 | }
1351 | println(a, b) // 0 1
1352 | ```
1353 |
1354 | - `if`判断语句类型断言
1355 |
1356 | ```go
1357 | package main
1358 |
1359 | func f0() int {return 333}
1360 |
1361 | func main() {
1362 | x := 9
1363 | checkType(x)
1364 | checkType(f0)
1365 | }
1366 |
1367 | func checkType(x any) {
1368 | // 断言传入的x为int类型,并获取值
1369 | if i, ok := x.(int); ok {
1370 | println("int: ", i) // int: 0
1371 | }
1372 |
1373 | if f, ok := x.(func() int); ok {
1374 | println("func: ", f()) // func: 333
1375 | }
1376 |
1377 | // 如果传入x类型为int,则可以直接获取其值
1378 | a := x.(int)
1379 | println(a)
1380 |
1381 | // 如果传入x类型不是byte,则会产生恐慌panic
1382 | b := x.(byte)
1383 | println(b)
1384 | }
1385 | ```
1386 |
1387 | ### **分支选择 switch, keyword:`switch` `case` `default` `fallthrough`**
1388 |
1389 | - `switch`存在分支选择对象时,`case`分支支持单个常量、常量列表
1390 |
1391 | ```go
1392 | switch x {
1393 | case 0:
1394 | println("single const")
1395 | case 1, 2, 3:
1396 | println("const list")
1397 | default:
1398 | println("default")
1399 | }
1400 | ```
1401 |
1402 | - 分支选择对象可以是一个表达式或语句的结果
1403 |
1404 | ```go
1405 | switch len(myIP) == net.IPv4len {
1406 | case true:
1407 | println("IPv4")
1408 | case false:
1409 | println("IPv6")
1410 | }
1411 |
1412 | switch isOddNumber(rand.Int()) {
1413 | case true:
1414 | println("Odd number")
1415 | case false:
1416 | println("Even Number")
1417 | }
1418 | ```
1419 |
1420 | - 分支选择对象之前可以有一个简单语句,case语句的大括号可以省略
1421 |
1422 | ```go
1423 | switch x *= 2; x {
1424 | case 4: {
1425 | println("single const")
1426 | }
1427 | case 5, 6, 7: {
1428 | println("const list")
1429 | }
1430 | default: {
1431 | println("default")
1432 | }
1433 | }
1434 | ```
1435 |
1436 | - `switch`只有一个简单语句,没有分支选择对象时,case分支支持逻辑表达式语句
1437 |
1438 | ```go
1439 | switch x /= 3; {
1440 | case x == 8:
1441 | println("expression")
1442 | case x >= 9:
1443 | println("expression")
1444 | default:
1445 | println("default")
1446 | }
1447 | ```
1448 |
1449 | - `switch`没有简单语句,没有分支选择对象时,case分支支持逻辑表达式语句
1450 |
1451 | ```go
1452 | switch {
1453 | case x == 10:
1454 | println("expression")
1455 | case x >= 11:
1456 | println("expression")
1457 | default:
1458 | println("default")
1459 | }
1460 | ```
1461 |
1462 | - `switch`类型分支,只能在switch语句中使用的`.(type)`获取对象的类型。
1463 |
1464 | ```go
1465 | package main
1466 |
1467 | import (
1468 | "fmt"
1469 | "go/types"
1470 | )
1471 |
1472 | func main() {
1473 | var (
1474 | a = 0.1
1475 | b = 2+3i
1476 | c = "asdf"
1477 | d = [...]byte{1, 2, 3}
1478 | e = []complex128{1+2i}
1479 | f = map[string]uintptr{"a": 0}
1480 | g = func(int) bool {return true}
1481 | h = struct { a, b int }{}
1482 | i = &struct {}{}
1483 | j chan int
1484 | k chan <- bool
1485 | l <-chan string
1486 | m types.Error
1487 | )
1488 |
1489 | values := []any{nil, a, b, &c, d, e, f, g, &g, h, &h, i, j, k, l, m}
1490 | for _, v := range values {
1491 | typeswitch(v)
1492 | }
1493 | }
1494 |
1495 | func typeswitch(x any) {
1496 | // switch x.(type) { // 不使用类型值时
1497 | switch i := x.(type) {
1498 | case nil:
1499 | fmt.Println("x is nil")
1500 | case int, int8, int16, rune, int64, uint, byte, uint16, uint32, uint64, float32, float64, complex64, complex128, uintptr, bool, string:
1501 | fmt.Printf("basic type : %T\n", i)
1502 | case *int, *int8, *int16, *rune, *int64, *uint, *byte, *uint16, *uint32, *uint64, *float32, *float64, *complex64, *complex128, *uintptr, *bool, *string:
1503 | fmt.Printf("basic pointer type : %T\n", i)
1504 | case [3]byte, []complex128, map[string]uintptr:
1505 | fmt.Printf("collection type : %T\n", i)
1506 | case func(i int) (b bool), *func():
1507 | fmt.Printf("function type : %T\n", i)
1508 | case struct {a, b int}, *struct {}:
1509 | fmt.Printf("struct type : %T\n", i)
1510 | case chan int, chan <- bool, <-chan string:
1511 | fmt.Printf("channel type : %T\n", i)
1512 | case error, interface{a(); b()}:
1513 | fmt.Printf("interface type : %T\n", i)
1514 | default:
1515 | fmt.Printf("other type : %T\n", i)
1516 | }
1517 | }
1518 |
1519 | // output:
1520 |
1521 | // x is nil
1522 | // basic type : float64
1523 | // basic type : complex128
1524 | // basic pointer type : *string
1525 | // collection type : [3]uint8
1526 | // collection type : []complex128
1527 | // collection type : map[string]uintptr
1528 | // function type : func(int) bool
1529 | // other type : *func(int) bool
1530 | // struct type : struct { a int; b int }
1531 | // other type : *struct { a int; b int }
1532 | // struct type : *struct {}
1533 | // channel type : chan int
1534 | // channel type : chan<- bool
1535 | // channel type : <-chan string
1536 | // interface type : errors.SignatureError
1537 | ```
1538 |
1539 | - `switch`中每个case分支默认带有break效果,一个分支执行后就跳出switch,不会自动向下执行其他case。
1540 | 使用`fallthrough`强制向下继续执行后面的case代码。
1541 | 在类型分支中不允许使用`fallthrough`语句
1542 |
1543 | ```go
1544 | switch {
1545 | case false:
1546 | println("case 1")
1547 | fallthrough
1548 | case true:
1549 | println("case 2")
1550 | fallthrough
1551 | case false:
1552 | println("case 3")
1553 | fallthrough
1554 | case true:
1555 | println("case 4")
1556 | case false:
1557 | println("case 5")
1558 | fallthrough
1559 | default:
1560 | println("default case")
1561 | }
1562 | // 输出:case 2 case 3 case 4
1563 | ```
1564 |
1565 | ### **循环语句 for, keyword:`for` `range` `continue` `break`**
1566 |
1567 | - Go只有一种循环结构:`for` 循环。
1568 | 可以让前置(初始化)、中间(条件)、后置(迭代)语句为空,或者全为空。
1569 |
1570 | ```go
1571 | for i := 0; i < 10; i++ {...}
1572 | for i := 0; i < 10; {...} // 省略迭代语句
1573 | for i := 0; ; i++; {...} // 省略条件语句
1574 | for ; i < 10; i++ {...} // 省略初始化语句
1575 | for i := 0; ; {...} // 省略条件和迭代语句, 分号不能省略
1576 | for ; i < 10; {...} // 省略初始化和迭代语句, 分号可省略
1577 | for ; ; i++ {...} // 省略初始化和条件语句, 分号不能省略
1578 | for i < 10 {...}
1579 | for ; ; {...} // 分号可省略
1580 | for {...}
1581 | ```
1582 |
1583 | - `for`语句中小括号 ( )是可选的,而大括号 { } 是必须的。
1584 |
1585 | ```go
1586 | for (i := 0; i < 10; i++) {...} // ❌编译错误.
1587 | for i := 0; (i < 10); i++ {...} // 编译通过.
1588 | for (i < 10) {...} // 编译通过.
1589 | ```
1590 |
1591 | - Go的for each循环`for range`
1592 |
1593 | ```go
1594 | a := [5]int{2, 3, 4, 5, 6}
1595 |
1596 | for k, v := range a {
1597 | fmt.Println(k, v) // 输出:0 2, 1 3, 2 4, 3 5, 4 6
1598 | }
1599 |
1600 | for k := range a {
1601 | fmt.Println(k) // 输出:0 1 2 3 4
1602 | }
1603 |
1604 | for _ = range a {
1605 | fmt.Println("print without care about the key and value")
1606 | }
1607 | ```
1608 |
1609 | `Go1.4+`
1610 | ```go
1611 | for range a {
1612 | fmt.Println("new syntax – print without care about the key and value")
1613 | }
1614 | ```
1615 |
1616 | - 循环的继续、中断、跳转
1617 |
1618 | ```go
1619 | for k, v := range s {
1620 | if v == 3 {
1621 | continue // 结束本次循环,进入下一次循环中
1622 | } else if v == 5 {
1623 | break // 结束整个for循环
1624 | } else {
1625 | goto SOMEWHERE // 跳转到标签指定的代码处
1626 | }
1627 | }
1628 | ```
1629 |
1630 | - `for range`只支持遍历`数组`、`数组指针`、`slice`、`string`、`map`、`channel`类型
1631 | 新增支持遍历整数类型,迭代从0到n-1递增的数字`Go1.22+`
1632 | 新增支持遍历函数迭代器`Go1.23+`
1633 |
1634 | ```go
1635 | package main
1636 |
1637 | import (
1638 | "fmt"
1639 | "slices"
1640 | )
1641 |
1642 | func main() {
1643 | var arr = [...]int{33, 22, 11, 0}
1644 | // 遍历数组,取一位值时为索引值
1645 | for k := range arr {
1646 | fmt.Printf("%d, ", k) // 0, 1, 2, 3,
1647 | }
1648 | fmt.Println()
1649 | // 遍历数组,取两位值时,第一位为索引值,第二位为元素值
1650 | for k, v := range arr {
1651 | fmt.Printf("%d %d, ", k, v) // 0 33, 1 22, 2 11, 3 0,
1652 | }
1653 | fmt.Println()
1654 |
1655 | // 遍历数组指针,取一位值时为索引值
1656 | for k := range &arr {
1657 | fmt.Printf("%d, ", k) // 0, 1, 2, 3,
1658 | }
1659 | fmt.Println()
1660 | // 遍历数组指针,取两位值时,第一位为索引值,第二位为元素值
1661 | for k, v := range &arr {
1662 | fmt.Printf("%d %d, ", k, v) // 0 33, 1 22, 2 11, 3 0,
1663 | }
1664 | fmt.Println()
1665 |
1666 | var slc = []byte{44, 55, 66, 77}
1667 | // 遍历切片,取一位值时为索引值
1668 | for k := range slc {
1669 | fmt.Printf("%d, ", k) // 0, 1, 2, 3,
1670 | }
1671 | fmt.Println()
1672 | // 遍历切片,取两位值时,第一位为索引值,第二位为元素值
1673 | for k, v := range slc {
1674 | fmt.Printf("%d %d, ", k, v) // 0 44, 1 55, 2 66, 3 77,
1675 | }
1676 | fmt.Println()
1677 |
1678 | var str = "abc一二3"
1679 | // 遍历字符串,取一位值时为字节索引值
1680 | for k := range str {
1681 | fmt.Printf("%d, ", k) // 0, 1, 2, 3, 6, 9,
1682 | }
1683 | fmt.Println()
1684 | // 遍历字符串,取两位值时,第一位为字节索引值,第二位为Unicode字符
1685 | for k, v := range str {
1686 | fmt.Printf("%d %d %s, ", k, v, string(v)) // 0 97 a, 1 98 b, 2 99 c, 3 19968 一, 6 20108 二, 9 51 3,
1687 | }
1688 | fmt.Println()
1689 |
1690 | var mp = map[int]string{5:"A", 9:"B"}
1691 | // 遍历map,取一位值时为键key
1692 | for k := range mp {
1693 | fmt.Printf("%d, ", k) // 9, 5,
1694 | }
1695 | fmt.Println()
1696 | // 遍历map,取两位值时,第一位为键key,第二位为元素值value
1697 | for k, v := range mp {
1698 | fmt.Printf("%d %s, ", k, v) // 5 A, 9 B,
1699 | }
1700 | fmt.Println()
1701 |
1702 | var ch = make(chan int)
1703 | go func() {
1704 | for i := 0; i < 5; i++ {
1705 | ch <- i
1706 | }
1707 | close(ch)
1708 | }()
1709 | // 遍历channel时,只能取一位值,为发送方发送到channel中的值
1710 | for x := range ch {
1711 | fmt.Printf("%d ", x) // 0 1 2 3 4
1712 | }
1713 | // 遍历整数时,只能取一位值,为从0到n-1之间的递增整数, 如果n<=0则不会执行遍历 Go1.22+
1714 | for i := range 5 {
1715 | fmt.Printf("%d ", i) // 0 1 2 3 4
1716 | }
1717 | // 遍历函数迭代器 Go1.23+
1718 | for v := range slices.Values([]int{1, 2, 3, 4, 5}) {
1719 | fmt.Println(v)
1720 | }
1721 | for i, v := range slices.Backward([]int{1, 2, 3, 4, 5}) {
1722 | fmt.Println(i, v)
1723 | }
1724 | }
1725 | ```
1726 |
1727 | ### **通道选择 select, keyword:`select`**
1728 |
1729 | - ` select`用于当前goroutine从一组可能的通讯中选择一个进一步处理。
1730 | 如果任意一个通讯都可以进一步处理,则从中随机选择一个,执行对应的语句。否则在没有默认分支(default case)时,select语句则会阻塞,直到其中一个通讯完成。
1731 | select 的 case 里的操作语句只能是IO操作
1732 |
1733 | ```go
1734 | ch1, ch2 := make(chan int), make(chan int)
1735 |
1736 | // 因为没有值发送到select中的任一case的channel中,此select将会阻塞
1737 | select {
1738 | case <-ch1:
1739 | println("channel 1")
1740 | case <-ch2:
1741 | println("channel 2")
1742 | }
1743 | ```
1744 |
1745 | ```go
1746 | ch1, ch2 := make(chan int), make(chan int)
1747 |
1748 | // 因为没有值发送到select中的任一case的channel中,此select将会执行default分支
1749 | select {
1750 | case <-ch1:
1751 | println("channel 1")
1752 | case <-ch2:
1753 | println("channel 2")
1754 | default:
1755 | println("default")
1756 | }
1757 | ```
1758 |
1759 | - select只会执行一次case分支的逻辑,与`for`组合使用实现多次遍历分支
1760 |
1761 | ```go
1762 | func main() {
1763 | for {
1764 | select {
1765 | case <-time.Tick(time.Second):
1766 | println("Tick")
1767 | case <-time.After(5 * time.Second):
1768 | println("Finish")
1769 | default:
1770 | println("default")
1771 | time.Sleep(5e8)
1772 | }
1773 | }
1774 |
1775 | ctx, cancel := context.WithTimeout(context.Background(), time.Second)
1776 | defer cancel()
1777 | loop:
1778 | for {
1779 | select {
1780 | case <-ctx.Done():
1781 | println("Tick")
1782 | break loop
1783 | case <-time.After(5 * time.Second):
1784 | println("Finish")
1785 | break loop
1786 | default:
1787 | println("default")
1788 | time.Sleep(5e8)
1789 | }
1790 | }
1791 | }
1792 | ```
1793 |
1794 | - select没有case分支可供选择执行时该调用会被永久阻塞,在其之后的语句将不会被执行
1795 |
1796 | ```go
1797 | select {}
1798 | ```
1799 |
1800 | ### **延迟执行 defer, keyword:`defer`**
1801 |
1802 | - `defer`语句调用函数,将调用的函数加入defer栈,栈中函数在defer所在的主函数返回时执行,执行顺序是先进后出/后进先出。
1803 |
1804 | ```go
1805 | package main
1806 |
1807 | func main() {
1808 | defer print(0)
1809 | defer print(1)
1810 | defer print(2)
1811 | defer print(3)
1812 | defer print(4)
1813 |
1814 | for i := 5; i <= 9; i++ {
1815 | defer print(i)
1816 | }
1817 | // 输出:9876543210
1818 | }
1819 | ```
1820 |
1821 | - defer在函数返回后执行,可以修改函数返回值
1822 |
1823 | ```go
1824 | package main
1825 |
1826 | func main() {
1827 | println(f()) // 返回: 15
1828 | }
1829 |
1830 | func f() (i int) {
1831 | defer func() {
1832 | i *= 5
1833 | }()
1834 | return 3
1835 | }
1836 | ```
1837 |
1838 | - defer用于释放资源
1839 |
1840 | 释放锁
1841 | ```go
1842 | mu.Lock()
1843 | defer mu.Unlock()
1844 | ```
1845 |
1846 | 关闭channel
1847 |
1848 | ```go
1849 | ch <- "hello"
1850 | defer close(ch)
1851 | ```
1852 |
1853 | 关闭IO流
1854 |
1855 | ```go
1856 | f, err := os.Open("file.xxx")
1857 | defer f.Close()
1858 | ```
1859 |
1860 | 关闭数据库连接
1861 |
1862 | ```go
1863 | db, err := sql.Open("mysql","user:password@tcp(127.0.0.1:3306)/hello")
1864 | if err != nil {
1865 | log.Fatal(err)
1866 | }
1867 | defer db.Close()
1868 | ```
1869 |
1870 | - defer用于恐慌的截获
1871 | `panic`用于产生恐慌,`recover`用于截获恐慌,recover只能在defer语句中使用, 直接调用recover是无效的。
1872 |
1873 | ```go
1874 | func main() {
1875 | f()
1876 | fmt.Println("main normal...")
1877 | }
1878 |
1879 | func f() {
1880 | defer func() {
1881 | if r := recover(); r != nil {
1882 | fmt.Println("catch:", r)
1883 | }
1884 | }()
1885 | p()
1886 | fmt.Println("normal...")
1887 | }
1888 |
1889 | func p() {
1890 | panic("exception...")
1891 | }
1892 | ```
1893 |
1894 | ### **跳转语句 goto, keyword:`goto`**
1895 |
1896 | - `goto`用于在一个函数内部运行跳转到指定标签的代码处,不能跳转到其他函数中定义的标签。
1897 |
1898 | - `goto`模拟循环
1899 |
1900 | ```go
1901 | package main
1902 |
1903 | func main() {
1904 | i := 0
1905 | loop:
1906 | i++
1907 | if i < 5 {
1908 | goto loop
1909 | }
1910 | println(i)
1911 | }
1912 | ```
1913 |
1914 | - `goto`模拟`continue`,`break`
1915 |
1916 | ```go
1917 | func main() {
1918 | i, sum := 0, 0
1919 | head:
1920 | for ; i <= 10; i++ {
1921 | if i < 5 {
1922 | i++ // 此处必须单独调用一次,因为goto跳转时不会执行for循环的自增语句
1923 | goto head // continue
1924 | }
1925 | if i > 9 {
1926 | goto tail // break
1927 | }
1928 | sum += i
1929 | }
1930 | tail:
1931 | println(sum) // 输出:35
1932 | }
1933 | ```
1934 |
1935 | - **注意**:任何时候都不建议使用`goto`
1936 |
1937 | ### 阻塞语句
1938 |
1939 | - 永久阻塞语句
1940 |
1941 | ```go
1942 | // 向一个未初始化的channel中写入数据会永久阻塞
1943 | (chan int)(nil) <- 0
1944 | // 从一个未初始化的channel中读取数据会永久阻塞
1945 | <-(chan struct{})(nil)
1946 | for range (chan struct{})(nil){}
1947 |
1948 | // select无任何选择分支会永久阻塞
1949 | select{}
1950 | ```
1951 |
1952 | ## **函数 Function**
1953 |
1954 | ### **函数声明 Declare, keyword:`func` `return`**
1955 |
1956 | - 使用关键字`func`声明函数,函数可以没有参数或接受多个参数
1957 |
1958 | ```go
1959 | func f0() {/*...*/}
1960 |
1961 | func f1(a int) {/*...*/}
1962 |
1963 | func f2(a int, b byte) {/*...*/}
1964 | ```
1965 |
1966 | - 在函数参数类型之前使用`...`声明该参数为可变数量的参数
1967 | 可变参数只能声明为函数的最后一个参数。
1968 |
1969 | ```go
1970 | func f3(a ...int) {/*...*/}
1971 |
1972 | func f4(a int, b bool, c ...string) {/*...*/}
1973 | ```
1974 |
1975 | - 函数可以返回任意数量的返回值
1976 |
1977 | ```go
1978 | func f0() {
1979 | return
1980 | }
1981 |
1982 | func f1() int {
1983 | return 0
1984 | }
1985 |
1986 | func f2() (int, string) {
1987 | return 0, "A"
1988 | }
1989 | ```
1990 |
1991 | - 函数返回结果参数,可以像变量那样命名和使用
1992 |
1993 | ```go
1994 | func f() (a int, b string) {
1995 | a = 1
1996 | b = "B"
1997 | return // 即使return后面没有跟变量,关键字在函数结尾也是必须的
1998 | // 或者 return a, b
1999 | }
2000 | ```
2001 |
2002 | - 当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略
2003 |
2004 | ```go
2005 | func f0(a,b,c int) {/*...*/}
2006 |
2007 | func f1() (a,b,c int) {/*...*/}
2008 |
2009 | func f2(a,b int, c,d byte) (x,y int, z,s bool) {/*...*/}
2010 | ```
2011 |
2012 | ### **函数闭包 Closure**
2013 |
2014 | - 匿名函数、闭包、函数值
2015 | Go中函数作为第一类对象,可以作为值对象赋值给变量
2016 | 可以在函数体外/内定义匿名函数,命名函数不能嵌套定义到函数体内,只能定义在函数体外
2017 |
2018 | ```go
2019 | package main
2020 |
2021 | type Myfunc func(i int) int
2022 |
2023 | func f0(name string){
2024 | println(name)
2025 | }
2026 |
2027 | func main() {
2028 | var a = f0
2029 | a("hello") // hello
2030 |
2031 | var f1 Myfunc = func(i int) int {
2032 | return i
2033 | }
2034 | fmt.Println(f1(3)) // 3
2035 |
2036 | var f2 func() int = func() int {
2037 | return 0
2038 | }
2039 | fmt.Println(f2()) // 0
2040 |
2041 | // 省略部分关键字
2042 | var f3 func() = func() {/*...*/}
2043 | var f4 = func() {/*...*/}
2044 | f5 := func() {/*...*/}
2045 | }
2046 | ```
2047 |
2048 | ### **内建函数 Builtin**
2049 |
2050 | - `func append`
2051 |
2052 | ```go
2053 | func append(slice []Type, elems ...Type) []Type
2054 | ```
2055 |
2056 | > 内建函数append将元素追加到切片的末尾。若它有足够的容量,其目标就会重新切片以容纳新的元素。否则,就会分配一个新的基本数组。append返回更新后的切片,因此必须存储追加后的结果。
2057 |
2058 | ```go
2059 | slice = append(slice, elem1, elem2)
2060 | slice = append(slice, anotherSlice...)
2061 | ```
2062 |
2063 | > 作为特例,可以向一个字节切片append字符串,如下:
2064 |
2065 | ```go
2066 | slice = append([]byte("hello "), "world"...)
2067 | ```
2068 |
2069 |
2070 | - `func cap`
2071 |
2072 | ```go
2073 | func cap(v Type) int
2074 | ```
2075 |
2076 | > 内建函数cap返回 v 的容量,这取决于具体类型:
2077 | 数组:v中元素的数量,与 len(v) 相同
2078 | 数组指针:*v中元素的数量,与len(v) 相同
2079 | 切片:切片的容量(底层数组的长度);若 v为nil,cap(v) 即为零
2080 | 信道:按照元素的单元,相应信道缓存的容量;若v为nil,cap(v)即为零
2081 |
2082 |
2083 |
2084 | - `func clear` `Go1.21+`
2085 |
2086 | ```go
2087 | func clear[T ~[]Type | ~map[Type]Type1](t T)
2088 | ```
2089 |
2090 | > 内建函数clear清除映射和切片。如果参数类型是类型参数,则类型参数的类型集必须仅包含映射或切片类型,并且clear执行类型参数隐含的操作。
2091 | 映射:删除所有元素从而产生空的映射map。
2092 | 切片:将切片长度以内的所有元素设置为相应元素类型的零值。
2093 |
2094 |
2095 |
2096 | - `func close`
2097 |
2098 | ```go
2099 | func close(c chan<- Type)
2100 | ```
2101 |
2102 | > 内建函数close关闭信道,该通道必须为双向的或只发送的。它应当只由发送者执行,而不应由接收者执行,其效果是在最后发送的值被接收后停止该通道。在最后的值从已关闭的信道中被接收后,任何对其的接收操作都会无阻塞的成功。对于已关闭的信道,语句:
2103 |
2104 | ```go
2105 | x, ok := <-c // ok值为false
2106 | ```
2107 |
2108 |
2109 | - `func complex`
2110 |
2111 | ```go
2112 | func complex(r, i FloatType) ComplexType
2113 | ```
2114 |
2115 | > 使用实部r和虚部i生成一个复数。
2116 |
2117 | ```go
2118 | c := complex(1, 2)
2119 | fmt.Println(c) // (1+2i)
2120 | ```
2121 |
2122 |
2123 | - `func copy`
2124 |
2125 | ```go
2126 | func copy(dst, src []Type) int
2127 | ```
2128 |
2129 | > 内建函数copy将元素从来源切片复制到目标切片中,也能将字节从字符串复制到字节切片中。copy返回被复制的元素数量,它会是 len(src) 和 len(dst) 中较小的那个。来源和目标的底层内存可以重叠。
2130 |
2131 | ```go
2132 | a, b, c := []byte{1, 2, 3}, make([]byte, 2), 0
2133 | fmt.Println("a:", a, " b:", b, " c: ", c) // a: [1 2 3] b: [0 0] c: 0
2134 |
2135 | c = copy(b, a)
2136 | fmt.Println("a:", a, " b:", b, " c: ", c) // a: [1 2 3] b: [1 2] c: 2
2137 |
2138 | b = make([]byte, 5)
2139 | c = copy(b, a)
2140 | fmt.Println("a:", a, " b:", b, " c: ", c) // a: [1 2 3] b: [1 2 3 0 0] c: 3
2141 |
2142 | s := "ABCD"
2143 | c = copy(b, s)
2144 | fmt.Println("s:", s, " b:", b, " c: ", c) // s: ABCD b: [65 66 67 68 0] c: 4
2145 | ```
2146 |
2147 |
2148 | - `func delete`
2149 |
2150 | ```go
2151 | func delete(m map[Type]Type1, key Type)
2152 | ```
2153 |
2154 | > 内建函数delete按照指定的键将元素从映射中删除。若m为nil或无此元素,delete不进行操作。
2155 |
2156 | ```go
2157 | m := map[int]string{
2158 | 0: "A",
2159 | 1: "B",
2160 | 2: "C",
2161 | }
2162 | delete(m, 1)
2163 | fmt.Println(m) // map[2:C 0:A]
2164 |
2165 | delete(m, 3) // 此行代码执行没有任何操作,也不会报错。
2166 | ```
2167 |
2168 |
2169 | - `func imag`
2170 |
2171 | ```go
2172 | func imag(c ComplexType) FloatType
2173 | ```
2174 |
2175 | > 返回复数c的虚部。
2176 |
2177 | ```go
2178 | c := 2+5i
2179 | fmt.Println(imag(c)) // 5
2180 | ```
2181 |
2182 |
2183 | - `func len`
2184 |
2185 | ```go
2186 | func len(v Type) int
2187 | ```
2188 |
2189 | > 内建函数len返回 v 的长度,这取决于具体类型:
2190 | 数组:v中元素的数量
2191 | 数组指针:*v中元素的数量(v为nil时panic)
2192 | 切片、映射:v中元素的数量;若v为nil,len(v)即为零
2193 | 字符串:v中字节的数量,计算字符数量使用`utf8.RuneCountInString()`
2194 | 通道:通道缓存中队列(未读取)元素的数量;若v为 nil,len(v)即为零
2195 |
2196 |
2197 |
2198 | - `func make`
2199 |
2200 | ```go
2201 | func make(Type, size IntegerType) Type
2202 | ```
2203 |
2204 | > 内建函数make分配并初始化一个类型为切片、映射、或通道的对象。其第一个实参为类型,而非值。make的返回类型与其参数相同,而非指向它的指针。其具体结果取决于具体的类型:
2205 | 切片:size指定了其长度。该切片的容量等于其长度。切片支持第二个整数实参可用来指定不同的容量;它必须不小于其长度,因此 make([]int, 0, 10) 会分配一个长度为0,容量为10的切片。
2206 | 映射:初始分配的创建取决于size,但产生的映射长度为0。size可以省略,这种情况下就会分配一个小的起始大小。
2207 | 通道:通道的缓存根据指定的缓存容量初始化。若 size为零或被省略,该信道即为无缓存的。
2208 |
2209 |
2210 |
2211 | - `func max` `Go1.21+`
2212 |
2213 | ```go
2214 | func max[T cmp.Ordered](x T, y ...T) T
2215 | ```
2216 |
2217 | > 内建函数max返回可排序类型的批量参数中的最大值。至少需要一个参数。如果传入参数是浮点类型并且有任一参数为NaN,则返回 NaN。
2218 |
2219 |
2220 |
2221 | - `func min` `Go1.21+`
2222 |
2223 | ```go
2224 | func min[T cmp.Ordered](x T, y ...T) T
2225 | ```
2226 |
2227 | > 内建函数min返回可排序类型的批量参数中的最小值。至少需要一个参数。如果传入参数是浮点类型并且有任一参数为NaN,则返回 NaN。
2228 |
2229 |
2230 |
2231 | - `func new`
2232 |
2233 | ```go
2234 | func new(Type) *Type
2235 | ```
2236 |
2237 | > 内建函数new分配内存。其第一个实参为类型,而非值。其返回值为指向该类型的新分配的零值的指针。
2238 |
2239 |
2240 |
2241 | - `func panic`
2242 |
2243 | ```go
2244 | func panic(v any)
2245 | ```
2246 |
2247 | > 内建函数panic停止当前Go程的正常执行。当函数F调用panic时,F的正常执行就会立刻停止。F中defer的所有函数先入后出执行后,F返回给其调用者G。G如同F一样行动,层层返回,直到该Go程中所有函数都按相反的顺序停止执行。之后,程序被终止,而错误情况会被报告,包括引发该恐慌的实参值,此终止序列称为恐慌过程。
2248 |
2249 |
2250 |
2251 | - `func print`
2252 |
2253 | ```go
2254 | func print(args ...Type)
2255 | ```
2256 |
2257 | > 内建函数print以特有的方法格式化参数并将结果写入标准错误,用于自举和调试。
2258 |
2259 |
2260 |
2261 | - `func println`
2262 |
2263 | ```go
2264 | func println(args ...Type)
2265 | ```
2266 |
2267 | > println类似print,但会在参数输出之间添加空格,输出结束后换行。
2268 |
2269 |
2270 |
2271 | - `func real`
2272 |
2273 | ```go
2274 | func real(c ComplexType) FloatType
2275 | ```
2276 |
2277 | > 返回复数c的实部。
2278 |
2279 | ```go
2280 | c := 2+5i
2281 | fmt.Println(real(c)) // 2
2282 | ```
2283 |
2284 |
2285 | - `func recover`
2286 |
2287 | ```go
2288 | func recover() any
2289 | ```
2290 |
2291 | > 内建函数recover允许程序管理恐慌过程中的Go程。在defer的函数中,执行recover调用会取回传至panic调用的错误值,恢复正常执行,停止恐慌过程。若recover在defer的函数之外被调用,它将不会停止恐慌过程序列。在此情况下,或当该Go程不在恐慌过程中时,或提供给panic的实参为nil时,recover就会返回nil。
2292 |
2293 | ### **初始化函数 init**
2294 |
2295 | - `init`函数是用于程序执行前做包的初始化工作的函数
2296 | `init`函数的声明没有参数和返回值
2297 |
2298 | ```go
2299 | func init() {
2300 | // ...
2301 | }
2302 | ```
2303 |
2304 | - 一个package或go源文件可以包含零个或多个init函数
2305 |
2306 | ```go
2307 | package main
2308 |
2309 | func main() {
2310 | }
2311 |
2312 | func init() {
2313 | println("init1...")
2314 | }
2315 | func init() {
2316 | println("init2...")
2317 | }
2318 | func init() {
2319 | println("init3...")
2320 | }
2321 | ```
2322 |
2323 | - init函数被自动调用,在main函数之前执行,不能在其他函数中调用,显式调用会报错该函数未定义。
2324 |
2325 | ```go
2326 | func init() {
2327 | println("init...")
2328 | }
2329 |
2330 | func main() {
2331 | init() // undefined: init
2332 | }
2333 | ```
2334 |
2335 | - 所有`init`函数都会被自动调用,调用顺序如下:
2336 | 1. 同一个go文件的init函数调用顺序是 **从上到下**的
2337 | 2. 同一个package中按go源文件名字符串比较 **从小到大**顺序调用各文件中的init函数
2338 | 3. 不同的package,如果不相互依赖的,按照main包中 **先import的后调用**的顺序调用其包中的init函数
2339 | 4. 如果package存在依赖,则先调用最早被依赖的package中的init函数
2340 |
2341 | ### **方法 Method**
2342 |
2343 | - 通过指定函数的接收者receiver,将函数绑定到一个类型或类型的指针上,使这个函数成为该类型的方法。
2344 | 只能对命名类型和命名类型的指针编写方法。
2345 | 只能在定义命名类型的那个包编写其方法。
2346 | 不能对接口类型和接口类型的指针编写方法。
2347 | 方法的接收者receiver是类型的值时,编译器会隐式的生成一个同名方法,其接收者receiver为该类型的指针,反过来却不会。
2348 |
2349 | ```go
2350 | package main
2351 |
2352 | type A struct {
2353 | x, y int
2354 | }
2355 |
2356 | // 定义结构体的方法,'_'表示方法内忽略使用结构体、字段及其他方法
2357 | func (_ A) echo_A() {
2358 | println("(_ A)")
2359 | }
2360 |
2361 | // 同上
2362 | func (A) echoA(s string) {
2363 | println("(A)", s)
2364 | }
2365 |
2366 | // 定义结构体指针的方法,'_'表示方法内忽略使用结构体指针、字段及其他方法
2367 | func (_ *A) echo_жA() {
2368 | println("(_ *A)")
2369 | }
2370 |
2371 | // 同上
2372 | func (*A) echoжA(s string) {
2373 | println("(*A)", s)
2374 | }
2375 |
2376 | // 定义结构体的方法,方法内可以引用结构体、字段及其他方法
2377 | func (a A) setX(x int) {
2378 | a.x = x
2379 | }
2380 |
2381 | // 定义结构体指针的方法,方法内可以引用结构体、结构体指针、字段及其他方法
2382 | func (a *A) setY(y int) {
2383 | a.y = y
2384 | }
2385 |
2386 | func main() {
2387 | var a A // a = A{}
2388 | a.setX(3)
2389 | a.setY(6)
2390 | println(a.x, a.y) // 0 6
2391 | a.echo_A() // (_ A)
2392 | a.echoA("a") // (A) a
2393 | a.echo_жA() // (_ *A)
2394 | a.echoжA("a") // (*A) a
2395 |
2396 | // 以下是定义在结构体值上的方法原型,通过调用结构体类型上定义的函数,传入结构体的值
2397 | A.echo_A(a) // (_ A)
2398 | A.echoA(a, "a") // (A) a
2399 | // A.echo_жA(a) // A.echo_жA未定义
2400 | // A.echoжA(a) // A.echoжA未定义
2401 | type AA = *A
2402 | AA.echo_жA(nil) // (_ *A)
2403 | A.setX(a, 4)
2404 | // A.setY(a, 7) // A.setY未定义
2405 | println(a.x) // 0
2406 |
2407 |
2408 | b := &a
2409 | b.setX(2)
2410 | b.setY(5)
2411 | println(b.x, b.y) // 0 5
2412 | b.echo_A() // (_ A)
2413 | b.echoA("b") // (A) b
2414 | b.echo_жA() // (_ *A)
2415 | b.echoжA("b") // (*A) b
2416 |
2417 | // 以下是定义在结构体指针上的方法原型,通过调用结构体类型指针上定义的函数,传入结构体的指针
2418 | (*A).echo_A(b) // (_ A)
2419 | (*A).echoA(b, "b") // (A) b
2420 | (*A).echo_жA(b) // (_ *A)
2421 | (*A).echoжA(b, "b") // (*A) b
2422 | (*A).setX(b, 1)
2423 | (*A).setY(b, 8)
2424 | println(b.x, b.y) // 0 8
2425 |
2426 | // 调用结构体空指针上的方法,以下注释掉的代码都是空指针错误
2427 | var c *A // c = nil
2428 | // c.setX(2) // ❌错误,nil pointer dereference
2429 | // c.setY(5) // ❌错误,nil pointer dereference
2430 | // println(c.x, c.y) // ❌错误,nil pointer dereference
2431 | // c.echo_A() // ❌错误,nil pointer dereference
2432 | // c.echoA() // ❌错误,nil pointer dereference
2433 | c.echo_жA() // (_ *A)
2434 | c.echoжA("c") // (*A) c
2435 |
2436 | // (*A).echo_A(c)
2437 | // (*A).echoA(c)
2438 | (*A).echo_жA(c) // (_ *A)
2439 | (*A).echoжA(c, "c") // (*A) c
2440 | // (*A).setX(c, 1)
2441 | // (*A).setY(c, 8)
2442 | // println(c.x, c.y)
2443 | }
2444 | ```
2445 |
2446 | - 结构体中组合匿名字段时,匿名字段的方法会向外传递,其规则如下:
2447 | 匿名字段为值类型时:值的方法会传递给结构体的值,指针的方法会传递给结构体的指针;
2448 | 匿名字段为指针类型时:指针的方法会传递给值和指针;
2449 | 匿名字段为接口类型时:方法会传递给值和指针;
2450 |
2451 | - Go中有匿名函数,但是没有匿名方法
2452 |
2453 | ## **并发 Concurrency, keyword:`go`**
2454 |
2455 | - 协程`goroutine`是由Go运行时环境管理的轻量级线程。
2456 | 使用关键字`go`调用一个函数/方法,启动一个新的协程goroutine
2457 |
2458 | ```go
2459 | package main
2460 |
2461 | import (
2462 | "time"
2463 | )
2464 |
2465 | func say(i int) {
2466 | println("goroutine:", i)
2467 | }
2468 |
2469 | func main() {
2470 | for i := 1; i <= 5; i++ {
2471 | go say(i)
2472 | }
2473 | say(0)
2474 | time.Sleep(5 * time.Second)
2475 | }
2476 | ```
2477 |
2478 | 主协程goroutine输出0,其他由go启动的几个子协程分别输出1~5
2479 |
2480 | > goroutine: 0
2481 | goroutine: 1
2482 | goroutine: 2
2483 | goroutine: 3
2484 | goroutine: 4
2485 | goroutine: 5
2486 |
2487 | - goroutine 在相同的地址空间中运行,因此访问共享内存必须进行同步。
2488 |
2489 | ```go
2490 | package main
2491 |
2492 | import (
2493 | "sync"
2494 | "time"
2495 | )
2496 |
2497 | var mu sync.Mutex
2498 | var i int
2499 |
2500 | func main() {
2501 | for range [5]byte{} {
2502 | go Add()
2503 | }
2504 | time.Sleep(5*time.Second)
2505 | println(i)
2506 | }
2507 |
2508 | func Add() {
2509 | // 使用互斥锁防止多个协程goroutine同时修改共享变量
2510 | // 只能限制同时访问此方法修改变量,在方法外修改则限制是无效的
2511 | mu.Lock()
2512 | defer mu.Unlock()
2513 | i++
2514 | }
2515 | ```
2516 |
2517 | 使用通道channel进行同步
2518 |
2519 | ```go
2520 | package main
2521 |
2522 | import (
2523 | "time"
2524 | )
2525 |
2526 | var i int
2527 | var ch = make(chan byte, 1)
2528 |
2529 | func main() {
2530 | for range [5]byte{} {
2531 | go Add()
2532 | }
2533 | time.Sleep(5*time.Second)
2534 | println(i)
2535 | }
2536 |
2537 | func Add() {
2538 | ch <- 0
2539 | i++
2540 | <-ch
2541 | }
2542 | ```
2543 |
2544 | - 使用channel在不同的goroutine之间通信
2545 |
2546 | ```go
2547 | // 上一个例子只是将channel用作同步开关,稍做修改即可在不同goroutine间通信
2548 | package main
2549 |
2550 | import (
2551 | "time"
2552 | )
2553 |
2554 | var i int
2555 | var ch = make(chan int, 1)
2556 |
2557 | func main() {
2558 | for range [5]byte{} {
2559 | go Add()
2560 | }
2561 | ch <- i
2562 | time.Sleep(5*time.Second)
2563 | i = <-ch
2564 | println(i)
2565 | }
2566 |
2567 | func Add() {
2568 | // 从channel中接收的值是来自其他goroutine发送的
2569 | x := <-ch
2570 | x++
2571 | ch <- x
2572 | }
2573 | ```
2574 |
2575 | ## **测试 Testing**
2576 |
2577 | - Go中自带轻量级的测试框架testing和自带的go test命令来实现单元测试和基准测试
2578 |
2579 | ### **单元测试 Unit**
2580 |
2581 | - 有如下待测试unittest包,一段简单的求和代码
2582 |
2583 | ```go
2584 | package testgo
2585 |
2586 | import "math"
2587 |
2588 | func Sum(min, max int) (sum int) {
2589 | if min < 0 || max < 0 || max > math.MaxInt32 || min > max {
2590 | return 0
2591 | }
2592 |
2593 | for ; min <= max; min++ {
2594 | sum += min
2595 | }
2596 | return
2597 | }
2598 | ```
2599 |
2600 | - 测试源文件名必须是`_test.go`结尾的,go test的时候才会执行到相应的代码
2601 | 必须import testing包
2602 | 所有的测试用例函数必须以`Test`开头
2603 | 测试用例按照源码中编写的顺序依次执行
2604 | 测试函数TestXxx()的参数是`*testing.T`,可以使用该类型来记录错误或者是测试状态
2605 | 测试格式:`func TestXxx (t *testing.T)`,Xxx部分可以为任意的字母数字的组合,首字母不能是小写字母[a-z],例如Testsum是错误的函数名。
2606 | 函数中通过调用*testing.T的Error,Errorf,FailNow,Fatal,FatalIf方法标注测试不通过,调用Log方法用来记录测试的信息。
2607 |
2608 | ```go
2609 | package testgo
2610 |
2611 | import "testing"
2612 |
2613 | func TestSum(t *testing.T) {
2614 | s := Sum(1, 0)
2615 | t.Log("Sum 1 to 0:", s)
2616 | if 0 != s {
2617 | t.Error("not equal.")
2618 | }
2619 | s = Sum(1, 10)
2620 | t.Log("Sum 1 to 10:", s)
2621 | if 55 != s {
2622 | t.Error("not equal.")
2623 | }
2624 | }
2625 | ```
2626 |
2627 | 在当前包中执行测试:`go test -v`
2628 |
2629 | > === RUN TestSum
2630 | --- PASS: TestSum (0.00s)
2631 | t0_test.go:7: Sum 1 to 0: 0
2632 | t0_test.go:12: Sum 1 to 10: 55
2633 | PASS
2634 | ok /home/yougg/code/unittest 0.004s
2635 |
2636 | ### **基准测试 Benchmark**
2637 |
2638 | - 基准测试 Benchmark用来检测函数/方法的性能
2639 | 基准测试用例函数必须以`Benchmark`开头
2640 | go test默认不会执行基准测试的函数,需要加上参数-test.bench
2641 | 语法:-test.bench="test_name_regex",例如go test -test.bench=".*"表示测试全部的基准测试函数
2642 | 在基准测试用例中,在循环体内使用testing.B.N,使测试可以正常的运行
2643 |
2644 | ```go
2645 | package testgo
2646 |
2647 | import "testing"
2648 |
2649 | func BenchmarkSum(b *testing.B) {
2650 | b.Logf("Sum 1 to %d: %d\n", b.N, Sum(1, b.N))
2651 | }
2652 | ```
2653 |
2654 | 在当前包中执行测试:`go test -v -bench .`
2655 |
2656 | > BenchmarkSum 2000000000 0.91 ns/op
2657 | --- BENCH: BenchmarkSum
2658 | t0_test.go:19: Sum 1 to 1: 1
2659 | t0_test.go:19: Sum 1 to 100: 5050
2660 | t0_test.go:19: Sum 1 to 10000: 50005000
2661 | t0_test.go:19: Sum 1 to 1000000: 500000500000
2662 | t0_test.go:19: Sum 1 to 100000000: 5000000050000000
2663 | t0_test.go:19: Sum 1 to 2000000000: 2000000001000000000
2664 | ok /home/yougg/code/benchmark 1.922s
2665 |
2666 | ### **模糊测试 Fuzzing**
2667 |
2668 | - 模糊测试针对测试运行输入随机数据,尝试找出漏洞或导致崩溃的输入
2669 | 可以通过模糊测试发现的一些漏洞示例包括 SQL 注入、缓冲区溢出、拒绝服务和跨站点脚本攻击。
2670 |
2671 | ```go
2672 | // 待测试函数
2673 | func Reverse(s string) string {
2674 | b := []byte(s)
2675 | for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
2676 | b[i], b[j] = b[j], b[i]
2677 | }
2678 | return string(b)
2679 | }
2680 | ```
2681 |
2682 | 模糊测试用例函数以`Fuzz`开头
2683 |
2684 | ```go
2685 | func FuzzReverse(f *testing.F) {
2686 | testcases := []string{"Hello, world", " ", "!12345"}
2687 | for _, tc := range testcases {f.Add(tc) // Use f.Add to provide a seed corpus
2688 | }
2689 | f.Fuzz(func(t *testing.T, orig string) {
2690 | rev := Reverse(orig)
2691 | doubleRev := Reverse(rev)
2692 | if orig != doubleRev {
2693 | t.Errorf("Before: %q, after: %q", orig, doubleRev)
2694 | }
2695 | if utf8.ValidString(orig) && !utf8.ValidString(rev) {
2696 | t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
2697 | }
2698 | })
2699 | }
2700 | ```
2701 |
2702 | 在当前包中执行测试:`go test -fuzz FuzzReverse`
2703 |
2704 | > fuzz: elapsed: 0s, gathering baseline coverage: 0/11 completed
2705 | failure while testing seed corpus entry: FuzzReverse/91f92b2e5d60254ba0d9f6ed565497fc550f81fe2c93c55c9a00034ffc3bceaf
2706 | fuzz: elapsed: 0s, gathering baseline coverage: 3/11 completed
2707 | --- FAIL: FuzzReverse (0.03s)
2708 | --- FAIL: FuzzReverse (0.00s)
2709 | hi_test.go:59: Reverse produced invalid UTF-8 string "\x81\xc7"
2710 | >
2711 | > FAIL
2712 | exit status 1
2713 | FAIL /home/yougg/code/fuzz 0.176s
2714 |
2715 | ## **泛型 Generics** `Go1.18+`
2716 |
2717 | ### **类型约束 type constraint**
2718 |
2719 | 
2720 |
2721 | - 定义约束指定类型`T`的`类型约束`接口
2722 |
2723 | ```go
2724 | type I10 interface {
2725 | int
2726 | }
2727 |
2728 | type I11 interface {
2729 | string
2730 | }
2731 | ```
2732 |
2733 | ```go
2734 | type S struct {
2735 | b bool
2736 | e error
2737 | }
2738 |
2739 | type I12 interface {
2740 | S
2741 | }
2742 | ```
2743 |
2744 | - 定义约束近似类型`~T`的`类型约束`接口
2745 |
2746 | ```go
2747 | type I20 interface {
2748 | ~int
2749 | }
2750 |
2751 | type I21 interface {
2752 | ~string
2753 | }
2754 | ```
2755 |
2756 | 约束近似类型的限制:
2757 |
2758 | - 近似类型`~T`必须是底层类型自身, 不能是基于底层类型自定义的新类型
2759 | 在[自定义类型](#自定义类型)小节中所有自定义的`A~Z`类型都被限制
2760 |
2761 | ```go
2762 | type I22 interface {
2763 | ~A // ❌错误: A的底层类型是 int
2764 | ~B // ❌错误: B的底层类型是 int8
2765 | ~C // ❌错误: C的底层类型是 int16
2766 | ~D // ❌错误: D的底层类型是 rune
2767 | ~E // ❌错误: E的底层类型是 int32
2768 | ~F // ❌错误: F的底层类型是 int64
2769 | ~G // ❌错误: G的底层类型是 uint
2770 | ~H // ❌错误: H的底层类型是 byte
2771 | ~I // ❌错误: I的底层类型是 uint16
2772 | ~J // ❌错误: J的底层类型是 uint32
2773 | ~K // ❌错误: K的底层类型是 uint64
2774 | ~L // ❌错误: L的底层类型是 float32
2775 | ~M // ❌错误: M的底层类型是 float64
2776 | ~N // ❌错误: N的底层类型是 complex64
2777 | ~O // ❌错误: O的底层类型是 complex128
2778 | ~P // ❌错误: P的底层类型是 uintptr
2779 | ~Q // ❌错误: Q的底层类型是 bool
2780 | ~R // ❌错误: R的底层类型是 string
2781 | ~S // ❌错误: S的底层类型是 [3]uint8
2782 | ~T // ❌错误: T的底层类型是 []complex128
2783 | ~U // ❌错误: U的底层类型是 map[string]uintptr
2784 | ~V // ❌错误: V的底层类型是 func(i int) (b bool)
2785 | ~W // ❌错误: W的底层类型是 struct {a, b int}
2786 | ~X // ❌错误: X的底层类型是 chan int
2787 | ~Y // ❌错误: Y的底层类型是 any
2788 | ~Z // ❌错误: Z的底层类型是 int
2789 | }
2790 |
2791 | // 正确的近似类型
2792 | type I23 interface {
2793 | ~int
2794 | ~int8
2795 | ~int16
2796 | ~rune
2797 | ~int32
2798 | ~int64
2799 | ~uint
2800 | ~byte
2801 | ~uint16
2802 | ~uint32
2803 | ~uint64
2804 | ~float32
2805 | ~float64
2806 | ~complex64
2807 | ~complex128
2808 | ~uintptr
2809 | ~bool
2810 | ~string
2811 | ~[3]uint8
2812 | ~[]complex128
2813 | ~map[string]uintptr
2814 | ~func(i int) (b bool)
2815 | ~struct {a, b int}
2816 | ~chan int
2817 | }
2818 | ```
2819 |
2820 | - 近似类型`~T`不能是接口类型
2821 |
2822 | ```go
2823 | type I24 interface {
2824 | ~error // ❌错误: 不能是接口类型
2825 | }
2826 | ```
2827 |
2828 | - 定义约束联合类型`A|B|C|~D`的`类型约束`接口
2829 |
2830 | ```go
2831 | type I30 interface {
2832 | int
2833 | string
2834 | }
2835 |
2836 | type I31 interface {
2837 | ~int
2838 | ~string
2839 | }
2840 |
2841 | type I32 interface {
2842 | int
2843 | string
2844 | ~int
2845 | ~string
2846 | }
2847 |
2848 | type Signed interface {
2849 | ~int | ~int8 | ~int16 | ~int32 | ~int64
2850 | }
2851 |
2852 | type Unsigned interface {
2853 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
2854 | }
2855 |
2856 | type Integer interface {
2857 | Signed | Unsigned
2858 | }
2859 |
2860 | type Number interface {
2861 | Integer
2862 | String() string
2863 | }
2864 | ```
2865 |
2866 | - `类型约束`接口的限制
2867 |
2868 | - 命名或匿名的`类型约束`接口只能用于`类型参数`声明
2869 |
2870 | - 不能用于全局/局部变量、函数/方法形参以及结构体字段的声明
2871 |
2872 | ```go
2873 | var i0 interface{ int } // ❌错误,不能做为全局变量的类型
2874 |
2875 | func Fn0() {
2876 | var i1 constraints.Integer // ❌错误,不能做为局部变量的类型
2877 | }
2878 |
2879 | func Fn1(i2 interface{ ~int }) { } // ❌错误,不能做为函数参数的类型
2880 |
2881 | var i3 struct{ F0 ~int } // ❌错误,不能做为结构体字段的类型
2882 | var i4 struct{ F1 constraints.Integer } // ❌错误
2883 | ```
2884 |
2885 | - `类型约束`接口的联合类型不能存在交集
2886 |
2887 | ```go
2888 | type I0 interface {
2889 | int | ~int // ❌错误,int和~int类型的交集是int
2890 | }
2891 | ```
2892 |
2893 | ### **类型参数 type parameters**
2894 |
2895 | 
2896 |
2897 | - `类型参数`声明在函数名与函数参数左括号之间, 为包含在`[]`中的一个或多个参数和约束类型
2898 |
2899 | ```go
2900 | // 为函数声明类型参数T, 其类型为any
2901 | func ZeroValue[T any]() {
2902 | var x T
2903 | fmt.Printf("zero value of %T type: %v\n", x, x)
2904 | }
2905 |
2906 | func main() {
2907 | ZeroValue[bool]() // zero value of bool type: false
2908 | ZeroValue[complex64]() // zero value of complex64 type: (0+0i)
2909 | ZeroValue[[3]int]() // zero value of [3]int type: [0 0 0]
2910 | ZeroValue[map[string]int]() // zero value of map[string]int type: map[]
2911 | ZeroValue[struct {
2912 | b bool
2913 | e error
2914 | }]() // zero value of struct { b bool; e error } type: {false }
2915 | }
2916 | ```
2917 |
2918 | `类型参数`作为函数输入参数或返回值的类型
2919 |
2920 | ```go
2921 | func Fn0[T, U any](t T, u U) {
2922 | fmt.Println(t, u)
2923 | }
2924 |
2925 | func Fn1[T constraints.Integer]() (t T) {
2926 | return T(rand.Int()) // 使用类型参数强制转换类型
2927 | }
2928 |
2929 | func Fn2[K comparable, V any](m map[K]V) {
2930 | fmt.Println(m)
2931 | }
2932 |
2933 | func main() {
2934 | Fn0[int, int](123, 456) // 123 456
2935 | Fn0[string, string]("hello", "world") // hello
2936 | fmt.Println(Fn1[int64]()) // 5577006791947779410
2937 | Fn2[int, int](map[int]int{1: 2, 3: 4}) // map[1:2 3:4]
2938 |
2939 | // 函数调用省略类型参数实参[...], Go自动推导其类型
2940 | Fn0(789, "xyz") // 789 xyz
2941 | Fn2(map[string]bool{"x": true, "y": false}) // map[x:true y:false]
2942 |
2943 | // 无法推导类型的场景不能省略类型参数实参
2944 | fmt.Println(Fn1[uint64]()) // 8674665223082153551
2945 | }
2946 | ```
2947 |
2948 | - `类型参数`声明在类型名右边, 为包含在`[]`中的一个或多个参数和约束类型
2949 |
2950 | ```go
2951 | type S[T any] struct {
2952 | Field T
2953 | }
2954 |
2955 | func (s *S[T])Set(t T) {
2956 | s.Field = t
2957 | }
2958 |
2959 | func (s *S[T]) Get() (t T) {
2960 | return s.Field
2961 | }
2962 |
2963 | func main() {
2964 | var s0 = new(S[int])
2965 | // s0.Field = 123
2966 | s0.Set(456)
2967 | fmt.Printf("%#v\n", s0)
2968 |
2969 | s1 := S[string]{
2970 | Field: "hello",
2971 | }
2972 | fmt.Printf("%#v\n", s1)
2973 | fmt.Printf("%#v\n", s1.Get())
2974 | }
2975 | ```
2976 |
2977 | - `类型参数`的类型支持指定类型、近似类型、联合类型、匿名和命名的`类型约束`接口
2978 |
2979 | ```go
2980 | func Fn10[T int, U uint]() { /* ... */ }
2981 | func Fn11[T ~string]() { /* ... */ }
2982 | func Fn12[T ~int | string]() { /* ... */ }
2983 | func Fn13[T interface{ int }]() { /* ... */ }
2984 | func Fn14[T constraints.Integer]() { /* ... */ }
2985 | ```
2986 |
2987 | - 基于`类型参数`定义`类型约束`接口
2988 |
2989 | ```go
2990 | type Slice[T any] interface {
2991 | ~[]T
2992 | }
2993 |
2994 | type Map[K comparable, V any] interface {
2995 | ~map[K]V
2996 | }
2997 |
2998 | type Chan[T any] interface {
2999 | ~chan T
3000 | }
3001 | ```
3002 |
3003 | - 基于`类型参数`定义泛型类型别名 `Go1.24+`
3004 |
3005 | ```go
3006 | type A[T comparable] int
3007 | type B[U int|byte|string] = A[U]
3008 | type C B[int]
3009 | ```
3010 |
3011 | - `类型参数`的限制
3012 |
3013 | - 不能将`类型参数`作为直接的约束类型
3014 |
3015 | ```go
3016 | func Fn0[T any, U T]() { } // ❌错误,不能将类型参数T做为类型参数U的约束类型
3017 | func Fn1[T any, U []T]() { } // 正确,约束的类型是[]T
3018 | func Fn2[T comparable, U any, V map[T]U]() { } // 正确,约束的类型是map[T]U
3019 | ```
3020 |
3021 | - 不能联合或嵌入`类型参数`和`comparable`作为约束的类型
3022 |
3023 | ```go
3024 | func Fn0[T any, U int | T]() { } // ❌错误
3025 | func Fn1[T any, U interface{ T }]() { } // ❌错误
3026 | func Fn2[T any, U interface{ T | string }]() { } // ❌错误
3027 | func Fn3[T comparable | int]() { } // ❌错误
3028 | func Fn4[T interface{ comparable }]() { } // ❌错误
3029 | ```
3030 |
3031 | - 联合的约束类型不能存在交集
3032 |
3033 | ```go
3034 | func Fn0[T int | ~int]() { } // ❌错误
3035 | func Fn1[T interface{ int | ~int }]() { } // ❌错误
3036 |
3037 | type Str string
3038 | func Fn2[T string | Str]() { } // 正确,Str和string是两个不同类型没有交集
3039 | func Fn3[T string | ~Str]() { } // ❌错误,~Str是string的近似类型存在交集string
3040 |
3041 | func Fn4[T byte | uint8]() { } // ❌错误,byte和uint8是相同的类型
3042 | ```
3043 |
3044 | - 联合的约束类型不能使用含有方法的接口类型
3045 |
3046 | ```go
3047 | func Fn0[T error | int]() { } // ❌错误,error接口含有方法
3048 | func Fn1[T interface{ string | io.Reader }]() { } // ❌错误
3049 | func Fn2[T interface{ io.Reader | io.Writer }]() { } // ❌错误
3050 | ```
3051 |
--------------------------------------------------------------------------------
/img/git0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yougg/gonote/93ce2b36eefa8ffbfb055e991e8ee3b527018405/img/git0.png
--------------------------------------------------------------------------------
/img/git1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yougg/gonote/93ce2b36eefa8ffbfb055e991e8ee3b527018405/img/git1.png
--------------------------------------------------------------------------------
/img/git2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yougg/gonote/93ce2b36eefa8ffbfb055e991e8ee3b527018405/img/git2.png
--------------------------------------------------------------------------------
/img/git3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yougg/gonote/93ce2b36eefa8ffbfb055e991e8ee3b527018405/img/git3.png
--------------------------------------------------------------------------------
/img/type_constraint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yougg/gonote/93ce2b36eefa8ffbfb055e991e8ee3b527018405/img/type_constraint.png
--------------------------------------------------------------------------------
/img/type_parameter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yougg/gonote/93ce2b36eefa8ffbfb055e991e8ee3b527018405/img/type_parameter.png
--------------------------------------------------------------------------------