├── Note
├── Hello-Go
│ ├── images
│ │ └── command-go.png
│ ├── GOROOT-GOPATH-GOBIN-and-workspace.md
│ ├── How-to-install-Golang.md
│ ├── Go-package.md
│ └── Go-commands.md
├── Concurrent-programming
│ ├── Overviews.md
│ ├── Timeout.md
│ ├── Buffered-Channels.md
│ ├── Range&Close.md
│ ├── Channels.md
│ ├── Runtime.md
│ ├── Select.md
│ └── Goroutine.md
└── Grammar-rules
│ ├── Fundamental-types.md
│ ├── Base-element.md
│ ├── Advanced-types.md
│ └── Flow-control.md
└── README.md
/Note/Hello-Go/images/command-go.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lita-jerry/Golang-note/HEAD/Note/Hello-Go/images/command-go.png
--------------------------------------------------------------------------------
/Note/Concurrent-programming/Overviews.md:
--------------------------------------------------------------------------------
1 | # 并发编程概述
2 |
3 | `Go` 的优势在于拥有基于多线程的并发方式, 在多进程与分布式方面, 也有很好标准库, 在多核时代总能事半功倍
4 |
5 | ## 并发编程基础
6 |
7 | `并发` 是指在同一个时间段内执行多个任务, 以便能够更快速的得到结果
8 |
9 |
--------------------------------------------------------------------------------
/Note/Concurrent-programming/Timeout.md:
--------------------------------------------------------------------------------
1 | # 超时
2 |
3 | 有时 `goroutine` 会进入阻塞状态, 为了避免整个程序进入阻塞状态, 可以使用 `select` 来设置超时:
4 |
5 | ``` Go
6 | package main
7 |
8 | import (
9 | "fmt"
10 | "time"
11 | )
12 |
13 | func main() {
14 | c := make(chan int)
15 | o := make(chan bool)
16 | go func() {
17 | for {
18 | select {
19 | case v := <- c:
20 | fmt.Println(v)
21 | case <- time.After(5 * time.Second):
22 | fmt.Println("timeout")
23 | o <- true
24 | break
25 | }
26 | }
27 | }()
28 | <- o
29 | }
30 | ```
--------------------------------------------------------------------------------
/Note/Concurrent-programming/Buffered-Channels.md:
--------------------------------------------------------------------------------
1 | # Buffered Channels
2 |
3 | Go允许指定 `channel` 的缓冲大小, 就是指明 `channel` 可以存储多少元素: `ch := make(chan string, 3)`, 创建了存储3个元素的 `string` 型 `channel`, 在这个 `channel` 中, 前3个元素可以无阻塞的写入, 当写入第4个元素时, 代码将会阻塞, 直到其他 `goroutine` 从 `channel` 中读取一些元素, 腾出元素空间
4 | ``` Go
5 | ch := make(chan type, value)
6 | ```
7 |
8 | 当 `value = 0` 时, `channel` 是无缓冲阻塞读写的,当 `value > 0` 时, `channel` 有缓冲、是非阻塞的, 直到写满 `value` 个元素才阻塞写入
9 |
10 | ``` Go
11 | package main
12 |
13 | import "fmt"
14 |
15 | func main() {
16 | c := make(chan int, 2)//修改2为1就报错,修改2为3可以正常运行
17 | c <- 1
18 | c <- 2
19 | fmt.Println(<-c)
20 | fmt.Println(<-c)
21 | }
22 | //修改为1报如下的错误:
23 | //fatal error: all goroutines are asleep - deadlock!
24 | ```
--------------------------------------------------------------------------------
/Note/Hello-Go/GOROOT-GOPATH-GOBIN-and-workspace.md:
--------------------------------------------------------------------------------
1 | # GOROOT GOPATH GOBIN与workspace
2 |
3 | ## GOROOT
4 | Golang环境的安装目录, Golang安装完成后就已经确定.
5 |
6 | ## GOPATH
7 | 指向workspace的路径, 需要将GOPATH路径添加到环境变量: $GOPATH
8 | > $GOPATH 是Go命令所依赖的重要环境变量.
9 | GOPATH是workspace的根目录, 包含以下文件夹
10 | - src 源文件的位置: .go, .c, .g, .s
11 | - pkg 已编译包(.a)的位置
12 | - bin Go构建可执行文件的位置
13 | go get 工具将包下载到GOPATH的第一目录
14 | > 从Go 1.8开始, 如果GOPATH未设置环境变量, 则默认为$HOME/go(Unix/Linux)和%USERPROFILE%/go(Windows).
15 | > 有些工具假设GOPATH只有一个目录.
16 |
17 | ## GOBIN
18 | 该文件夹用于放置go install 和go get编译main包后生成的二进制文件
19 |
20 | ## workspace
21 | 项目的工作空间, 即存放项目工程的位置, GOPATH是指向workspace根目录的路径.
22 |
23 | ## 面试可能会提问:
24 | Q: 设置GOPATH的意义?
25 | A: 为了集中组织代码和项目工程, 以及代码间相互引用.
26 |
27 | Q: 工作区是指? GOPATH是指?
28 | A: 工作区是指存放工程文件的位置, GOPATH是指向工作区的根目录的路径.
29 |
30 | Q: 编译后的.a文件是什么?
31 | A: .a文件是在Linux下编译的归档文件, 也就是archive文件, 是程序编译后生成的静态库文件.
32 |
--------------------------------------------------------------------------------
/Note/Concurrent-programming/Range&Close.md:
--------------------------------------------------------------------------------
1 | # Range和Close
2 |
3 | 我们使用 `Channel` 的 `Buffered` 时, 需要确定读取的次数, 很不方便, 所以也可以通过 `range` 操作 `slice` 或者 `map` 一样操作缓存类型的 `channel`
4 |
5 | ``` Go
6 | package main
7 |
8 | import (
9 | "fmt"
10 | )
11 |
12 | func printEvenNumber(n int, c chan int) {
13 | number, i := 0, 0
14 | for ; i < n; number++ {
15 | if (number % 2 == 0) {
16 | c <- number
17 | i++
18 | }
19 | }
20 | close(c)
21 | }
22 |
23 | func main() {
24 | c := make(chan int, 10)
25 | go printEvenNumber(100, c)
26 | for i := range c {
27 | fmt.Println(i)
28 | }
29 | }
30 | ```
31 | `for i := range c` 能够不断地读取 `channel` 传递的数据, 直到该 `channel` 被显式的关闭. 在消费方可以通过语法 `v, ok := <- ch` 中的 `ok` 查询 `channel` 是否被关闭, 如果 `ok` 为 `false`, 则表示 `channel` 已经没有任何数据并已经被关闭
32 | > 应确保在生产者中关闭 `channel` 而不是在消费方, 否则容易引起 `pannic`
33 | > `channel` 不像文件操作需要显式关闭, 当你确认没有任何发送的数据了, 或者想显式结束 `range` 循环, 或者想显式的结束 `channel`, 则可以调用 `close` 关闭
--------------------------------------------------------------------------------
/Note/Concurrent-programming/Channels.md:
--------------------------------------------------------------------------------
1 | # Channels
2 |
3 | Go提供了一个很好的通信机制 `channel`, 可以与Unix shell中的双向管道做类比: 可以通过它发送或接收值
4 |
5 | 值只能是特定的类型: `channel` 类型, 定义一个 `channel` 时, 也需要定义发送到 `channel` 的值的类型, 使用 `make` 创建channel:
6 | ``` Go
7 | ci := make(chan int)
8 | cs := make(chan string)
9 | cf := make(chan interface{})
10 | ```
11 |
12 | `channel` 通过操作符 `<-` 才发送和接收数据
13 | ``` Go
14 | cs <- v //将v发送到channel cs
15 | v <- cs //接收channel cs的数据
16 | ```
17 |
18 | 展示一个实际例子:
19 | ``` Go
20 | package main
21 |
22 | import "fmt"
23 |
24 | func sum(a []int, c chan int) {
25 | total := 0
26 | for _, v := range a {
27 | total += v
28 | }
29 | c <- total // send total to c
30 | }
31 |
32 | func main() {
33 | a := []int{7, 2, 8, -9, 4, 0}
34 |
35 | c := make(chan int)
36 | go sum(a[:len(a)/2], c)
37 | go sum(a[len(a)/2:], c)
38 | x, y := <-c, <-c // receive from c
39 |
40 | fmt.Println(x, y, x + y)
41 | }
42 | ```
43 | > 默认情况下, `channel` 的的发送和接收数据都是阻塞的, 除非另一端已经准备好, 这样就使得 `goroutines` 同步变的更加的简单, 而不需要显式的 `lock`
44 |
--------------------------------------------------------------------------------
/Note/Concurrent-programming/Runtime.md:
--------------------------------------------------------------------------------
1 | # Runtime Goroutine
2 |
3 | 在执行一些高并发的计算任务时, 为了尽量利用高性能服务器多核心的特性, 将计算任务并行化, 从而达到降低总计算时间的目的
4 |
5 | ## Gosched
6 |
7 | 出让时间片函数, 在设计并发任务时, 用户可以在每个 `goroutine` 中控制何时主动让出时间片给其他 `goroutine`, 这可以使用 `Gosched()` 函数实现:
8 | ``` Go
9 | package main
10 |
11 | import (
12 | "fmt"
13 | "runtime"
14 | )
15 |
16 | func say(s string) {
17 | for i := 0; i < 5; i++ {
18 | runtime.Gosched()
19 | fmt.Println(s)
20 | }
21 | }
22 |
23 | func main() {
24 | go say("world") //开一个新的Goroutines执行
25 | say("hello") //当前Goroutines执行
26 | fmt.Scanln() //防止未执行完毕就退出程序
27 | fmt.Println("quit")
28 | }
29 |
30 | //以上输出:
31 | // hello
32 | // world
33 | // hello
34 | // world
35 | // hello
36 | // world
37 | // hello
38 | // world
39 | // hello
40 | // world
41 |
42 | // quit
43 | ```
44 | 理论上讲应该如上所示的输出, 但是也会根据运行环境的不同, 输出 `hello` 与 `world` 的顺序可能也会不同
45 |
46 | ## NumCPU
47 |
48 | 有时为了将多个并发执行的 `goroutine` 分配给不同的CPU核心去完成, 用户就需要知道CPU核心的具体数目. 为此, `runtime` 包提供了 `NumCPU()` 函数可以完成这个任务
49 |
50 | 为了观察系统任务调度情况, 还可以使用 `NumGoroutine()` 函数返回正在执行和排队的任务总数
51 |
52 | ## Goexit
53 |
54 | 终止当前 `goroutine`, 如果要强行终止一个 `goroutine` 的执行, 可以调用 `Goexit()` 函数来完成, `Goexit()` 将终止整个堆栈链, 并在内层退出, 但是 `defer` 语句仍然或被执行
55 |
--------------------------------------------------------------------------------
/Note/Concurrent-programming/Select.md:
--------------------------------------------------------------------------------
1 | # Select
2 |
3 | 如果存在多个 `channel`, Go提供了一个关键字 `select` 用于监听 `channel` 上的数据流动
4 |
5 | `select` 默认是阻塞的, 只有当监听的 `channel` 中有发送或接收的时候才会运行
6 |
7 | > 当多个 `channel` 都准备好的时候, `select` 是随机选择一个执行的
8 |
9 | ``` Go
10 | package main
11 |
12 | import (
13 | "fmt"
14 | )
15 |
16 | func printEvenNumber(c chan int, quit chan int) {
17 | for i := 0;; i++ {
18 | if (i % 2 == 0) {
19 | select {
20 | case c <- i:
21 | case <- quit:
22 | return
23 | default:
24 | fmt.Println("当c阻塞的时候执行这里")
25 | }
26 | }
27 | }
28 | }
29 |
30 | func main() {
31 | c := make(chan int, 10)
32 | quit := make(chan int, 1)
33 |
34 | go func() {
35 | for i := 0; i < 5; i++ {
36 | fmt.Println(<- c)
37 | }
38 | quit <- 0
39 | }()
40 |
41 | printEvenNumber(c, quit)
42 | }
43 |
44 | //程序输出:
45 | // 当c阻塞的时候执行这里
46 | // 当c阻塞的时候执行这里
47 | // 当c阻塞的时候执行这里
48 | // 当c阻塞的时候执行这里
49 | // 当c阻塞的时候执行这里
50 | // 当c阻塞的时候执行这里
51 | // 当c阻塞的时候执行这里
52 | // 当c阻塞的时候执行这里
53 | // 当c阻塞的时候执行这里
54 | // 当c阻塞的时候执行这里
55 | // 当c阻塞的时候执行这里
56 | // 当c阻塞的时候执行这里
57 | // 0
58 | // 2
59 | // 4
60 | // 6
61 | // 8
62 | ```
63 |
64 | `select` 中还有 `default` 语法, 当所有的 `channel` 都没有准备好时, 默认执行 `default` 中的语句, 此时不在阻塞等待
65 |
--------------------------------------------------------------------------------
/Note/Concurrent-programming/Goroutine.md:
--------------------------------------------------------------------------------
1 | # Goroutine
2 |
3 | `Goroutine` 其实就是协成, 但是比线程更小, 十几个 `goroutine ` 在底层体现就是五六个线程, `Go` 语言内部实现了这些 `Goroutine` 之间的内存共享
4 |
5 | 执行 `goroutine` 只需要极少的栈内存(大概4~5KB), 当然会根据相应的数据伸缩, 也正是因此, 可同时运行成千上万个并发任务, `goroutine ` 比 `thread` 更易用、更高效、更轻便
6 |
7 | `goroutine` 是通过 `Go` 的 `runtime` 管理的一个线程管理器, `goroutine` 通过 `go` 关键字实现, 其实就是一个普通的函数:
8 | ``` Go
9 | go hello(name)
10 | ```
11 |
12 | 来看一个具体的例子:
13 | ``` Go
14 | package main
15 |
16 | import (
17 | "fmt"
18 | "runtime"
19 | )
20 |
21 | func say(s string) {
22 | for i := 0; i < 5; i++ {
23 | runtime.Gosched()
24 | fmt.Println(s)
25 | }
26 | }
27 |
28 | func main() {
29 | go say("world") //开一个新的Goroutines执行
30 | say("hello") //当前Goroutines执行
31 | fmt.Scanln() //防止未执行完毕就退出程序
32 | fmt.Println("quit")
33 | }
34 |
35 | //以上输出: hello与world的打印顺序可能因为运行环境不同而不同
36 | // hello
37 | // world
38 | // hello
39 | // world
40 | // hello
41 | // world
42 | // hello
43 | // world
44 | // hello
45 | // world
46 |
47 | // quit
48 | ```
49 | > runtime.Gosched()表示出让时间片, 让出当前 `goroutine` 执行权限, 调度器会安排其他等待的任务去运行, 并在下轮某个时间片再从该位置恢复执行
50 | 多个 `goroutine` 运行在同一个进程里面, 共享内存数据, 不过设计上我们要遵循 `不要通过共享来通信, 而是要通过通信来共享`
51 | > 默认情况下, 在 `Go 1.5` 将标识并发系统个数的`runtime.GOMAXPROCS`的初始值由 `1` 改为了运行环境的CPU核心数, 如果在 `Go 1.5` 及其以前的环境使用线程, 需要显示的配置核心数量: `runtime.GOMAXPROCS(n)`
52 |
--------------------------------------------------------------------------------
/Note/Hello-Go/How-to-install-Golang.md:
--------------------------------------------------------------------------------
1 | # 如何安装Golang环境
2 |
3 | ## 下载安装包
4 |
5 | [官网下载](https://golang.google.cn/dl/)
6 |
7 | ## 傻瓜式安装
8 |
9 | macOS为例, 安装至 /usr/local/go
10 |
11 | ## 配置环境变量
12 |
13 | > 可以写个配置脚本
14 |
15 | ``` sh
16 | vim ~/.bash_profile
17 | ```
18 |
19 | > 写入环境变量
20 |
21 | ```
22 | export GOPATH=/Users/YouName/GolangWorkSpace/go
23 | export GOBIN=/Users/YouName/GolangWorkSpace/go/bin
24 | export PATH=$PATH:$GOBIN
25 | ```
26 |
27 | > 执行脚本
28 |
29 | ``` sh
30 | source ~/.bash_profile
31 | ```
32 |
33 | > 查看当前环境变量
34 |
35 | ``` sh
36 | go env
37 | ```
38 |
39 | > 指令出现当前Golang的环境变量, 主要查看GOROOT、GOPATH、GOBIN
40 |
41 | ```
42 | GOROOT="/usr/local/go"
43 | GOPATH="/Users/YouName/GolangWorkSpace/go"
44 | GOBIN="/Users/YouName/GolangWorkSpace/go/bin"
45 | ```
46 |
47 | ## 编写第一个Go程序
48 |
49 | > 创建工程
50 |
51 | ``` sh
52 | cd $GOPATH
53 | mkdir src bin pkg
54 | cd src/
55 | mkdir hello
56 | cd hello/
57 | vim main.go
58 | ```
59 |
60 | > 在main.go中编写helloworld代码
61 |
62 | ```
63 | package main
64 |
65 | import (
66 | "fmt"
67 | )
68 |
69 | func main() {
70 | fmt.Println("hello world!")
71 | }
72 | ```
73 |
74 | > 编译安装
75 |
76 | ``` sh
77 | go install
78 | ```
79 |
80 | > 生成的可执行文件hello, 存放在$GOPATH/bin下, 可运行查看结果
81 |
82 | ``` sh
83 | $GOPATH/bin/hello
84 | ```
85 |
86 | ## 关于IDE和插件
87 |
88 | 支持jetbrains全家桶(按年付费, 刚买的...肉疼): [GoLand](https://www.jetbrains.com/go/)
89 |
90 | LiteIDE是一款专门为Go语言开发的跨平台轻量级集成开发环境(IDE), 由visualfc编写: [GitHub](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/01.4.md)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Golang学习笔记
2 |
3 | ## Hello Go
4 |
5 | ### [如何安装Golang环境](https://github.com/JerrySir/Golang-note/blob/master/Note/Hello-Go/How-to-install-Golang.md)
6 |
7 | ### [GOROOT GOPATH GOBIN与workspace](https://github.com/JerrySir/Golang-note/blob/master/Note/Hello-Go/GOROOT-GOPATH-GOBIN-and-workspace.md)
8 |
9 | ### [Go命令](https://github.com/JerrySir/Golang-note/blob/master/Note/Hello-Go/Go-commands.md)
10 |
11 | ### [Go包](https://github.com/JerrySir/Golang-note/blob/master/Note/Hello-Go/Go-package.md)
12 |
13 |
14 | ## 语法
15 |
16 | ### [基本元素](https://github.com/JerrySir/Golang-note/blob/master/Note/Grammar-rules/Base-element.md)
17 |
18 | ### [基本类型](https://github.com/JerrySir/Golang-note/blob/master/Note/Grammar-rules/Fundamental-types.md)
19 |
20 | ### [高级类型](https://github.com/JerrySir/Golang-note/blob/master/Note/Grammar-rules/Advanced-types.md)
21 |
22 | ### [流程控制](https://github.com/JerrySir/Golang-note/blob/master/Note/Grammar-rules/Flow-control.md)
23 |
24 | ## 并发编程
25 |
26 | ### [概述](https://github.com/JerrySir/Golang-note/blob/master/Note/Concurrent-programming/Overviews.md)
27 |
28 | ### [Goroutine](https://github.com/JerrySir/Golang-note/blob/master/Note/Concurrent-programming/Goroutine.md)
29 |
30 | ### [Channels](https://github.com/JerrySir/Golang-note/blob/master/Note/Concurrent-programming/Channels.md)
31 |
32 | ### [Buffered Channels](https://github.com/JerrySir/Golang-note/blob/master/Note/Concurrent-programming/Buffered-Channels.md)
33 |
34 | ### [Range和Close](https://github.com/JerrySir/Golang-note/blob/master/Note/Concurrent-programming/Range&Close.md)
35 |
36 | ### [Select](https://github.com/JerrySir/Golang-note/blob/master/Note/Concurrent-programming/Select.md)
37 |
38 | ### [Runtime](https://github.com/JerrySir/Golang-note/blob/master/Note/Concurrent-programming/Runtime.md)
--------------------------------------------------------------------------------
/Note/Hello-Go/Go-package.md:
--------------------------------------------------------------------------------
1 | # 包
2 |
3 | ## 基本规则
4 |
5 | - 同目录下的源码文件, 代码包声明语句要一致, 也就是说, 它们要属于同一个代码包, 这对于所有源码文件都是适用的.
6 | - 源码文件中, 声明的包的名字, 可以与其所在目录的名字不同.
7 |
8 | 每个Go程序都是由包构成的, 且都是从main开始加载, 在载入程序之前, 会执行各个程序包内init方法, 在执行main方法时, 已经执行完成所有程序包的init方法
9 |
10 | ``` Go
11 | package main
12 |
13 | import (
14 | "fmt"
15 | "math/rand"
16 | )
17 |
18 | func main() {
19 | fmt.Println("The random number is", rand.Intn(10))
20 | }
21 | ```
22 |
23 | 本程序通过导入路径 "fmt" 和 "math/rand" 两个包.
24 |
25 | 按照约定, 包名与导入路径最后一个元素一致, 例如"math/rand"包中源码均以`package rand`语句开始.
26 |
27 | > 此程序的运行环境是固定的, 因此`rand.Intn`总是会返回相同的数字(要得到不同的数字, 需为生成器提供不同的种子数, 参见[rand.Seed](https://go-zh.org/pkg/math/rand/#Seed) 练习场中的时间为常量, 因此你需要用其它的值作为种子数)
28 |
29 | 如果需要导入多个包时, 需要用括号括起来, 且每个包名独占一行
30 |
31 | 在使用包中公开的程序实体时, 需要使用包路径最后一个元素加`.`符号访问公开实体, 例如程序中`rand.Intn(10)`方式访问`rand`包中的`Intn`方法
32 |
33 | 同一个源码文件中导入多个包的最后一个元素不能重复(包括只引用不使用的情况), 否则编译错误, 但可以为导入的包起个别名:
34 |
35 | ``` Go
36 | import (
37 | "xxx1/package"
38 | package2 "xxx2/package"
39 | )
40 | ```
41 |
42 | 之后就可以使用`package2.xxx`调用该包的实体
43 |
44 | 如果不想加前缀而直接使用某个包中的实体, 可以使用`.`来代替别名, 例如:
45 |
46 | ``` Go
47 | package main
48 |
49 | import (
50 | "fmt"
51 | . "math/rand"
52 | )
53 |
54 | func main() {
55 | fmt.Println("The random number is", Intn(10))
56 | }
57 | ```
58 |
59 | 如果只是想初始化某个包, 而不需要在当前源码文件中使用, 可以使用`_`来代替别名, 例如:
60 |
61 | ``` Go
62 | package main
63 |
64 | import (
65 | "fmt"
66 | _ "math/rand"
67 | )
68 |
69 | func main() {
70 | fmt.Println("HelloWorld")
71 | }
72 | ```
73 |
74 | ## 关于实体
75 |
76 | Go中的变量、常量、函数和类型声明可统称为程序实体, 而他们的名称都成为标识符
77 |
78 | 标识符可以使Unicode字符集中任意自然语言文字的字符、数字及下划线`_`, 标识符不能以数字或下划线开头
79 |
80 | ## 关于访问权限
81 |
82 | 标识符的首字母控制着对应程序实体的访问权限, 如果标识符的首字母是大写, 则表示它所对应的程序实体可以被引入该包的程序访问、使用, 也称为可导出或者公开的; 否则, 对应的程序实体就只能被本包内的代码访问, 也称为不可导出的或包级私有的
83 |
84 | ## 包初始化
85 |
86 | ``` Go
87 | func init() {
88 | fmt.Println("Initialize...")
89 | }
90 | ```
91 |
92 | `init`方法在执行`main`方法之前
--------------------------------------------------------------------------------
/Note/Grammar-rules/Fundamental-types.md:
--------------------------------------------------------------------------------
1 | # Go基本类型
2 |
3 | | 名称 | 宽度(字节) | 零值 | 说明 |
4 | | ------------ | ------------ | ------------ | ------------ |
5 | | bool | 1 | false | 布尔类型 |
6 | | byte | 1 | 0 | 字节类型, 也可看作是一个由8位二进制数表示的无符号整数类型 |
7 | | rune | 4 | 0 | rune类型, 用于存储Unicode字符, 也可看作是一个由32位二进制数表示的有符号整数类型 |
8 | | int/uint | - | 0 | 有符号整数类型/无符号整数类型, 宽度与其平台有关 |
9 | | int8/uint8 | 1 | 0 | 由8位二进制数表示的有符号整数类型/无符号整数类型 |
10 | | int16/uint16 | 2 | 0 | 由16位二进制数表示的有符号整数类型/无符号整数类型 |
11 | | int32/uint32 | 4 | 0 | 由32位二进制数表示的有符号整数类型/无符号整数类型 |
12 | | int64/uint64 | 8 | 0 | 由64位二进制数表示的有符号整数类型/无符号整数类型 |
13 | | float32 | 4 | 0.0 | 由32位二进制数表示的浮点数类型 |
14 | | float64 | 8 | 0.0 | 由64位二进制数表示的浮点数类型 |
15 | | complex64 | 8 | 0.0 + 0.0i | 由64位二进制数表示的复数类型, 它由float32类型的实部和虚部联合表示 |
16 | | complex128 | 16 | 0.0 + 0.0i | 由128位二进制数表示的复数类型, 它由float64类型的实部和虚部联合表示 |
17 | | string | - | "" | 字符串类型, 一旦创建, 其内容不可改变 |
18 |
19 | 基本类型共`18`个, 其中`int`和`uint`的实际宽度会根据计算架构的不同而不同(386: 32比特, 4字节; amd64/x86-64: 64比特, 8字节)
20 |
21 | byte可看作类型`uint8`的别名类型, 而`rune`可看作`int32`的别名类型, rune类型专用于存储Unicode编码的单个字符, 我们可以用5中方法表示一个`rune`字面量:
22 |
23 | - 所对应的字符, 必须是Unicode编码规范所支持的
24 | - 使用 `\x` 为前导并后跟2位十六进制数, 这种方式可以表示宽度为1字节的值, 即一个ASCII编码值
25 | - 使用 `\` 为前导并后跟3位八进制数, 这种表示法也只能表示有限宽度的值, 即它只能用于表示在0和255之间的值, 它与上一个表示法的表示范围是一致的
26 | - 使用 `\u` 为前导并后跟4位十六进制数, 它只能用于表示2字节宽度的值
27 | - 使用 `\U` 为前导并后跟8位十六进制数, 它只能用于表示4字节宽度的值, 这种方式即为Unicode编码规范中的UCS-4表示法
28 |
29 | 此外, rune字面量还支持一类特殊的字符序列————转义符, 转义符的表示方式是在 `\` 后面追加一个特定的单字符:
30 |
31 | | 转义符 | Unicode代码点 | 说明 |
32 | | ------------ | ------------ | ------------ |
33 | | `\a` | U+0007 | 告警铃声或蜂鸣声 |
34 | | `\b` | U+0008 | 退格符 |
35 | | `\f` | U+000C | 换页符 |
36 | | `\n` | U+000A | 换行符 |
37 | | `\r` | U+000D | 回车符 |
38 | | `\t` | U+0009 | 水平制表符 |
39 | | `\v` | U+000b | 垂直制表符 |
40 | | `\\` | U+005c | 反斜杠 |
41 | | `\'` | U+0027 | 单引号, 仅在rune字面量中有效 |
42 | | `\"` | U+0022 | 双引号, 仅在string字面量中有效 |
43 |
44 | 除了上述转义符外, rune字面量中以 `\` 为前导的字符序列都是不合法的
45 |
46 | 字符串字面量有两种表示形式:
47 | - 原生字符串字面量: 由反引号 `\`` 包裹
48 | - 解释型字符串字面量: 由双引号 `"` 包裹
49 |
50 | 只有基本类型及其别名类型才可以作为常量的类型, 常量声明使用关键字 `const`, 一般形式: `const 常量名 常量类型 = 常量值`
51 |
52 | ``` Go
53 | const DEFAULT_IP string = '192.168.0.1'
54 | const DEFAULT_PORT int = 8080
55 | ```
56 |
57 | 可以简写成:
58 |
59 | ``` Go
60 | const (
61 | DEFAULT_IP string = '192.168.0.1'
62 | DEFAULT_PORT int = 8080
63 | )
64 | ```
65 |
--------------------------------------------------------------------------------
/Note/Hello-Go/Go-commands.md:
--------------------------------------------------------------------------------
1 | # Go命令
2 |
3 | ## 用法
4 |
5 | ``` sh
6 | go [arguments]
7 | ```
8 |
9 | 可通过 `go` 命令查看
10 |
11 | 
12 |
13 | 使用 `go help ` 查看该命令的更多信息.
14 |
15 | 接下来我们将介绍一些常用命令及参数的使用方法.
16 |
17 | ## go build
18 |
19 | 编译包和依赖项
20 |
21 | ``` sh
22 | go build [-o output] [-i] [build flags] [packages]
23 | ```
24 |
25 | - 可单独对一个go文件执行build命令, 也可对一个目录执行build命令, 如果对一个目录执行, 则会对整个目录的go文件进行编译, 同目录下的源码文件的包声明要一致.
26 |
27 | - 如果是普通包(非main), 执行`go build`命令将不会产生文件, 如需要生成文件, 则需要使用`go install`, 生成的对应文件在`$GOPATH/pkg`
28 |
29 | - 如果是main包, 执行`go build`后将在当前目录产生执行文件, 如果需要在`$GOPATH/bin`下生成执行文件, 则需要执行`go install`或`go build -o /path/xx.a`(Windows环境下为xx.exe)
30 |
31 | - `go build`会忽略目录下以'_'或者'.'开头以及以'_test.go'结尾的go文件.
32 |
33 | - 可以针对不同系统编译不同的文件, `go build`将会选择性的编译以系统名为后缀名的文件, 例如xx_linux.go xx_darwin.go xx_windows.go xx_freebsd.go 文件, 如果在Linux环境下编译, 则只编译xx.linux.go, 其他文件将忽略.
34 |
35 | | 标记名称 | 标记描述 |
36 | | ------------ | ------------ |
37 | | -a | 完全编译, 更新全部至最新包, 如果不但要编译依赖的包, 还要安装他们的执行文件, 可以加入标记`-i` |
38 | | -n | 打印编译命令但不执行 |
39 | | -p n | 以n核CPU并行编译, 默认为本机CPU核数 |
40 | | -race | 开启编译的时自动检测数据竞争的情况, 目前只支持64位的机器(linux/amd64, freebsd/amd64, darwin/amd64 windows/amd64) |
41 | | -msan | 启用[memory sanitizer](https://github.com/google/sanitizers/wiki/MemorySanitizer)的互操作, 只支持linux/amd64, linux/arm64 并且只有 Clang/LLVM 作为主C编译器的平台 |
42 | | -v | 在编译的时候打印包的名字 |
43 | | -work | 打印临时编译目录的名称, 并且在编译结束退出后不删除该目录 |
44 | | -x | 打印编译命令, 可以看到具体执行了哪些操作 |
45 | | -o | 指定输出路径、文件名 |
46 | | -i | 编译并且安装包, 可以理解为`go build` + `go install` |
47 |
48 | ## go install
49 |
50 | 编译并安装包及其依赖项
51 |
52 | ``` sh
53 | go install [-i] [build flags] [packages]
54 | ```
55 |
56 | 该命令在内部执行分为两个步骤
57 |
58 | - 编译生成可执行文件
59 | - 将生成的文件从临时文件夹移动到`$GOPATH/pkg`或`$GOPATH/bin`中.
60 |
61 | > `go install`命令参数与`go build`的参数基本相同.
62 |
63 | ## go get
64 |
65 | 下载并安装包和依赖项
66 |
67 | ``` sh
68 | go get [-d] [-f] [-t] [-u] [-v] [-fix] [-insecure] [build flags] [packages]
69 | ```
70 |
71 | `go get`执行两步操作, 第一将资源文件下载下来, 第二步执行`go install`
72 |
73 | 工具会根据域名的不同调用不同的下载工具:
74 |
75 | | 资源域名 | 下载工具 |
76 | | ------------ | ------------ |
77 | | GitHub | Git |
78 | | BitBucket | Git, Mercurial |
79 | | Google Code Project Hosting | Git, Mercurial, Subversion |
80 | | Launchpad | Bazaar |
81 |
82 | | 标记名称 | 标记描述 |
83 | | ------------ | ------------ |
84 | | -d | 表示只下载, 但不安装 |
85 | | -u | 让命令利用网络来更新已有代码包及其依赖包, 默认情况下, 该命令只会从网络上下载本地不存在的代码包, 而不会更新已有的代码包 |
86 | | -f | 仅在使用`-u`标记时才有效, 该标记会让命令程序忽略掉对已下载代码包的导入路径的检查, 如果下载并安装的代码包所属的项目是你从别人那里Fork过来的, 那么这样做就尤为重要了 |
87 | | -fix | 包下载完成后, 先执行修正动作, 而后再进行编译和安装 |
88 | | -t | 同时下载测试所需的包 |
89 | | -v | 显示执行的命令 |
90 | | -insecure | 表示可以使用非安全的scheme(如HTTP)下载包, 如下载包所有的网络协议不支持HTTPS, 则可以添加该标记, 并在安全的网络环境使用 |
91 |
92 | > `go get`命令还支持`go build`的参数, 详细请参考`go help build`.
93 |
94 | ## go doc 与 godoc
95 |
96 | 打印Go语言程序实体的文档, Go1.2版本后将`go doc`移到`godoc`中, 需要安装:`go get golang.org/x/tools/cmd/godoc`
97 |
98 | ``` sh
99 | go doc [-u] [-c] [package|[package.]symbol[.methodOrField]]
100 | ```
101 |
102 | > 所谓程序实体, 是指变量、常量、函数、结构体以及接口
103 |
104 | 启动本地web版文档程序:
105 | ``` sh
106 | godoc -http=:8888
107 | ```
108 |
109 | 浏览器中打开`127.0, 0.1:8888`
110 |
111 | | 标记名称 | 标记描述 |
112 | | ------------ | ------------ |
113 | | -c | 区分大小写, 默认情况是对程序实体大小写是不敏感的 |
114 | | -cmd | 同时打印出main包中的可导出的程序实体(名称首字母为大写)的文档, 默认不打印该部分文档 |
115 | | -u | 同时打印出不可导出的程序实体(名称首字母为大写)的文档, 默认不打印该部分文档 |
116 |
117 | ## go env
118 |
119 | 打印Go语言的环境信息
120 |
121 | ``` sh
122 | go env [-json] [var ...]
123 | ```
124 |
125 | 如果加入`-json`标记, 则按json格式打印Go环境信息.
126 |
127 | 更新信息可输入`go help environment`查看
128 |
129 | ## 其他命令
130 |
131 | | 命令名称 | 命令原型 | 命令描述 | 帮助文档 |
132 | | ------------ | ------------ | ------------ | ------------ |
133 | | `go fix` | `go fix [packages]` | 更新包以使用最新的API | `go help packages` |
134 | | `go run` | `go run [build flags] [-exec xprog] package [arguments...]` | 编译并运行程序 | `go help build` |
135 | | `go version` | `go version` | 查看当前Go版本 |
136 | | `go list` | `go list [-f format] [-json] [-m] [list flags] [build flags] [packages]` | 查看当前已经安装的包列表 |
137 |
138 | 更多关于Go命令请参考[The Go Programming Language](https://golang.org/cmd/go/)
139 |
--------------------------------------------------------------------------------
/Note/Grammar-rules/Base-element.md:
--------------------------------------------------------------------------------
1 | # Go基本元素
2 |
3 | ## 标识符
4 |
5 | 同一代码块中不允许出现同名的程序实体
6 |
7 | 大小写用于区分访问权限
8 |
9 | 预定义标识符:
10 |
11 | - 所有基本数据类型的名称
12 | - 接口类型error
13 | - 常量true、false和iota
14 |
15 | 所有内建函数的名称: `append`、`cap`、`close`、`complex`、`copy`、`delete`、`imag`、`len`、`make`、`new`、`panic`、`print`、`println`、`real`和`recover`
16 |
17 | > 空标识符`_`用在变量声明或代码包导入, 若声明一个变量而不使用, 则编译器会报错, 使用空标识符`_`就可以绕过编译器检查, 不会产生任何操作
18 |
19 | ## 关键字
20 |
21 | 关键字是被编程语言保留的字符串序列, 不能把他们用作标识符, 因此也叫保留字
22 |
23 | | 类别 | 关键字 |
24 | | ------------ | ------------ |
25 | | 程序声明 | `import`和`package` |
26 | | 程序实体声明和定义 | `chan`、`const`、`func`、`interface`、`map`、`struct`、`type`和`var` |
27 | | 程序流程控制 | `go`、`select`、`break`、`case`、`continue`、`default`、`defer`、`else`、`fallthrough`、`for`、`goto`、`if`、`range`、`return`和`switch` |
28 |
29 | Go的关键字共有`25`个, 其中与并发编程有关的关键字有`go`、`chan`、`select`
30 |
31 | 常用关键字及其解释:
32 | - var 变量声明
33 | - const 常量声明
34 | - package 包声明
35 | - import 包引入
36 | - func 定义函数和方法
37 | - return 函数返回
38 | - defer 类似析构函数, 在函数结束前执行
39 | - go 以协成的方式运行
40 | - select 选择不同类型的通讯
41 | - interface 定义接口
42 | - struct 定义抽象数据类型
43 | - break、case、continue、for、fallthrough、else、if、switch、goto、default 流程控制
44 | - chan channel通讯
45 | - type 声明自定义类型
46 | - map 声明map类型数据
47 | - range 读取slice、map、channel数据
48 |
49 | 特别说明一下关键字`type` ———— 类型声明, 用于声明一个自定义类型:
50 |
51 | ``` Go
52 | type myString string
53 | ```
54 |
55 | 这里将`myString`的类型声明为`string`类型的一个别名类型, 反过来说, `string`类型是`myString`类型的潜在类型
56 |
57 | 虽然类型及其潜在类型是不同的两个类型, 但是他们的值可以相互转换, 不产生新值: `string(myString("ABC"))`
58 |
59 | ## 字面量
60 |
61 | 简单来说, 字面量的意思就是值的一种标记法
62 |
63 | 常用的字面量有以下三种:
64 |
65 | - 用于表示基础数据类型值的各种字面量, 例如表示浮点数类型值的`3E-2`
66 | - 用于构造各种自定义的复合数据类型的类型字面量, 例如用于表示一个名称为`People`的结构体类型:
67 |
68 | ``` Go
69 | type People struct {
70 | Name string
71 | Sex int
72 | }
73 | ```
74 |
75 | - 用于表示符合数据类型的值的复合字面量
76 |
77 | ``` Go
78 | People{Name: "ZhangSan", Sex: 1}
79 | ```
80 |
81 | 其中People表示这个值的类型, 紧随其后的就是由键值对表示的复合元素列表
82 |
83 | ## 操作符
84 |
85 | 也成运算符, 用于执行特定算术或逻辑操作的符号, 操作的对象称为操作数
86 |
87 | | 符号 | 说明 | 示例 |
88 | | ------------ | ------------ | ------------ |
89 | | `\|\|` | 逻辑或操作, 既是二元操作符, 同时也属于逻辑操作符 | `true \|\| false // 结果是true` |
90 | | `&&` | 逻辑与操作, 既是二元操作符, 同时也属于逻辑操作符 | `true && false // 结果是false` |
91 | | `==` | 相等判断操作, 既是二元操作符, 同时也属于比较操作符 | `"abc" == "abc" // 结果是true` |
92 | | `!=` | 不等判断操作, 既是二元操作符, 同时也属于比较操作符 | `"abc" != "abc" // 结果是false` |
93 | | `>` | 大于判断操作, 既是二元操作符, 同时也属于比较操作符 | `3 > 2 // 结果是true` |
94 | | `>=` | 大于或等于判断操作, 既是二元操作符, 同时也属于比较操作符 | `3 >= 2 // 结果是true` |
95 | | `+` | 求和操作, 既是一元操作符又是二元操作符, 同时也属于算数操作符, 若作为一元操作符, 此操作不会对原值产生影响 | `+1 // 结果是1`
`1 + 2 // 结果是3` |
96 | | `-` | 求差操作, 既是一元操作符又是二元操作符, 同时也属于算数操作符, 若作为一元操作符, 则表示求反操作 | `-1 // 结果是-1(1的相反数)`
`1 - 2 // 结果是-1` |
97 | | `\|` | 按位或操作, 既是二元操作符, 同时也属于算数操作符 | `3 \| 5 // 结果是7` |
98 | | `^` | 按位异或操作, 既是一元操作符又是二元操作符, 同时也属于算数操作符, 若作为一元操作符, 则表示按位补码操作 | `3 ^ 5 // 结果是6`
`^6 结果是-7` |
99 | | `*` | 求乘积操作, 既是一元操作符又是二元操作符, 同时也属于算数操作符和地址操作符, 若作为地址操作符, 则表示取值操作 | `*p // 若p为指向整数类型值为3的指针, 则结果为3`
`3 * 5 // 结果是15` |
100 | | `/` | 求商操作, 既是二元操作符, 同时也属于算数操作符 | `9 / 3 // 结果是3` |
101 | | `%` | 求余数操作, 既是二元操作符, 同时也属于算数操作符 | `6 % 5 // 结果是1` |
102 | | `<<` | 按位左移操作, 既是二元操作符, 同时也属于算数操作符 | `4 << 2 // 结果是16` |
103 | | `>>` | 按位右移操作, 既是二元操作符, 同时也属于算数操作符 | `4 >> 2 // 结果是1` |
104 | | `&` | 按位与操作, 既是一元操作符又是二元操作符, 同时也属于算数操作符和地址操作符, 若作为地址操作符, 则表示取址操作 | `&V // 结果为标识符V值在内存中的地址`
`5 | 6 // 结果是4` |
105 | | `&^` | 按位清除操作, 既是二元操作符, 同时也属于算数操作符 | `5 &^ 11 // 结果是4` |
106 | | `!` | 逻辑非操作, 既是一元操作符, 同时也属于逻辑操作符 | `!false // 结果是true` |
107 | | `<-` | 接收操作, 既是一元操作符, 同时也属于接收操作符 | `<- ch // 若ch表示元素类型为type的通道类型值, 则此表达式就表示从ch中接收一个byte类型值的操作` |
108 |
109 | Go的操作符一共有`21`个, 并分为`5`类: `算数操作符`、 `比较操作符`、 `逻辑操作符`、 `地址操作符`、 `接收操作符`
110 |
111 | 当一个表达式中存在多个操作符时, 将涉及到操作顺序的问题, 一元操作符拥有最高的优先级, 而二元操作符执行优先级如下表所示:
112 |
113 | | 优先级 | 操作符 |
114 | | ------------ | ------------ |
115 | | 5 | `*`、 `/`、 `%`、 `<<`、 `>>`、 `&` 和 `&^` |
116 | | 4 | `+`、 `-`、 `\|` 和 `^` |
117 | | 3 | `==`、 `!=`、 `<`、 `<=`、 `>` 和 `>=` |
118 | | 2 | `&&` |
119 | | 1 | `\|\|` |
120 |
121 | 优先级数字越大, 表示优先级越高
122 |
123 | > 注意: `++` 和 `--` 是语句而不是表达式, 因此他们不存在于任何操作符优先层次内, 例如 `*p++` 等同于 `(*p)++`
124 |
125 | ## 表达式
126 |
127 | 表达式是把操作符和函数作用于操作数的计算方法, 在Go中, 表达式是构成具有词法意义的代码的最基本元素, 以下列出表达式的种类:
128 |
129 | | 种类 | 用途 | 示例 |
130 | | ------------ | ------------ | ------------ |
131 | | 选择表达式 | 选择一个值中的字段或方法 | `context.Speaker // context是变量名` |
132 | | 索引表达式 | 选取数组、切片、字符串或字典值中的某个元素 | `array1[1] // array表示一个数组且长度必须大于1` |
133 | | 切片表达式 | 选取数组、数组指针、切片或字符串值中的某个范围的元素 | `slice1[0:2] // slice表示一个切片值, 其容量必须大于或等于2` |
134 | | 类型断言 | 判断一个接口值的实际类型是否为某个类型, 或一个非接口值的类型是否实现了某个接口类型 | `v1.(I1) // v1表示一个接口值, I1表示一个接口类型` |
135 | | 调用表达式 | 调用一个函数或一个值的方法 | `v1.M1() // v1表示一个值, M1表示与该值关联的一个方法` |
136 |
137 | 关于类型断言, 有两点需要注意:
138 |
139 | - 如果v1是一个非接口值, 那么必须在做类型断言之前把它转换成接口值, 因为Go中的任何类型都是空接口类型的实现类型, 所以一般会这样做: `interface{}(v1)(I1)`
140 |
141 | - 如果类型断言的结果为`false`, 就意味着该类型断言是失败的, 失败的类型断言会引发一个运行时恐慌(或称运行时异常), 解决办法是:
142 |
143 | ``` Go
144 | var i1, ok = interface{}(v1)(I1)
145 | ```
146 |
147 | 这里声明并赋值了两个变量, 其中`ok`是布尔类型的变量, 它的值体现了类型断言的成败, 如果成功, `i1`就会是经过类型转换后的`I1`类型的值, 否则它将会是`I1`类型的零值(或称默认值), 此时当断言失败时, 运行时恐慌就不会发生
148 |
149 | 关键字`var`用于变量的声明, 在它和等号`=`之间可以有多个由逗号隔开的变量名, 这种在一条语句中同时为多个变量赋值的方法, 叫做`平行赋值`. 另外, 如果在声明变量的同时进行赋值, 那么等号左边的变量类型可以省略
150 |
151 | ``` Go
152 | var i1 I1
153 | var ok bool
154 | i1, ok = interface{}(v1).(I1)
155 | // 以上可以简写为:
156 | i1, ok := interface{}(v1).(I1)
157 | ```
158 |
159 | 这种简写方式只能出现在函数中, 有了符号`:=`, 关键字`var`也可以省略, 这叫`短变量声明`
160 |
--------------------------------------------------------------------------------
/Note/Grammar-rules/Advanced-types.md:
--------------------------------------------------------------------------------
1 | # Go高级类型
2 |
3 | Go的高级数据类型相当于自定义数据类型的模板或制作工具
4 |
5 | ## 数组
6 |
7 | 数组(array)是由若干相同类型的元素组成的序列
8 |
9 | ``` Go
10 | var ipv4 [4]uint8 = [4]uint8{192, 168, 0, 1}
11 | ```
12 |
13 | 如果在函数体内, 可省略 `var` 关键字, 但赋值号必须由 `=` 变为 `:=`
14 |
15 | 数组长度也是数组类型的一部分, 只要数组长度不同, 即使是两个数组类型的元素类型相同, 他们也是不同的类型
16 |
17 | 与string类型一样, 一旦在声明中确定了数组类型的长度, 就无法改变它了
18 |
19 | 数组类型的零值一定是一个不包含任何元素的空数组, 一个类型的零值即为该类型变量未被显示赋值时的默认值, 以ipv4为例, 其所属类型的零值就是`[4]uint8{}`
20 |
21 | 在上面示例中, 等号右边的字面量表示该类型的一个值, 可以忽略在方括号中表示数组长度的正整数值:
22 | ``` Go
23 | [...]uint8{192, 168, 0, 1}
24 | ```
25 | 方括号中的特殊标记 `...` 表示需要由Go编译器计算该值的元素数量并以此获得其长度
26 |
27 | `索引表达式` 和 `切片表达式` 都可用于数组操作, 前者获取数组值中的一个元素, 后者获取一个元素类型与之相同的切片值
28 |
29 | 此外 `len` 和 `cap` 也都可以应用于数组值, 并都可以得到其长度
30 |
31 | 当需要详细规划程序所用的内存时, 数组类型非常有用, 使用数组值可以完全避免内存的二次分配操作, 因为它的长度是不可变的
32 |
33 | ## 切片
34 |
35 | 切片(slice)可以看做是一种对数组的包装形式, 它包装的数组称为该切片的底层数组. 反过来讲, 切片是针对其底层数组中某个连续片段的描述
36 |
37 | ``` Go
38 | var ips = []string{"192.168.0.1", "192.168.0.2", "192.168.0.3"}
39 | ```
40 |
41 | 与数组不同, 切片的类型字面量(如`[]string`)并不携带长度信息. 切片的长度是可变的, 并且不是类型的一部分; 只要元素类型相同, 两个切片的类型就是相同的. 此外, 一个切片类型的零值总是 `nil`, 此零值的长度和容量都为0
42 |
43 | 切片值相当于对某个底层数组的引用, 其内部结构包含了3个元素:
44 | - 指向底层数组中某个元素的指针
45 | - 切片的长度
46 | - 切片的容量
47 |
48 | > 这里所说的容量是指: 从指针指向的那个元素到底层数组的最后一个元素的元素个数
49 |
50 | 切片值的容量意味着, 在不更换底层数组的前提下, 它的长度的最大值. 可通过 `cap` 函数和切片表达式, 在此前提下最大化一个切片值的长度: `ips[:cap(ips)]`
51 |
52 | > 简单来说, 切片的截取表达含义, 例如: `ips[0:2]`, 其含义为从切片下标 `0` 开始, 到下标 `2` 之前的这一段数据, 即: `ips[0]、ips[1]`
53 |
54 | 除 `len` 和 `cap`, 内建函数 `append` 也可应用于切片值: `ips = append(ips, "192.168.0.4")`
55 | [append文档](https://golang.org/pkg/builtin/#append)
56 |
57 | 另一个值得提的内置函数是 `make`, 它用于初始化切片、字典或通道类型的值, 对于切片类型来说, 用 `make` 函数的好处就是可以用很短的代码初始化一个长度很大的值: `ips = make([]string, 100)`, 其中100个元素的值都是string的零值: 空字符串""
58 |
59 | To learn more about slices, read the [Slices: usage and internals](https://blog.golang.org/go-slices-usage-and-internals) article.
60 |
61 | ## 字典
62 |
63 | Go中字典类型是散列表(hash table)的一个实现, 其官方称为 `map`, 散列表是一个实现了关联数组的数据结构, 关联数组是用于表示键值对的无序集合的一种抽象数据类型. Go中称键值对为 `键-元素对`, 它把字典值中的每个键都看作与其对应的元素的索引, 这样的索引在同一个字典值中是唯一的
64 |
65 | ``` Go
66 | var ipSwitches = map[string]bool{}
67 | ```
68 |
69 | 变量 `ipSwitches` 的键类型为 string, 元素类型为 bool, `map[string]bool{}` 表示了一个不包含任何元素的字典值
70 |
71 | 与切片类型一样, 字典类型也是一个引用类型, 字典类型的零值是 `nil`, 字典值的长度表示了其中的建-元素对的数量, 其零值的长度总是0
72 |
73 | 索引表达式可用于字典值中键-元素的添加和修改:
74 | ``` Go
75 | ipSwitches["192.168.0.1"] = true // 不存在该键, 添加元素值
76 | ipSwitches["192.168.0.1"] = false // 已存在该键, 修改元素值
77 | delete(ipSwitches, "192.168.0.1") // 删除元素, 无论是否存在键, 都会执行完毕
78 | ```
79 |
80 | ## 函数和方法
81 |
82 | 在Go中, 函数类型是一等类型, 意味着可以把函数当作一个值来传递和使用, 函数值既可以作为其他函数的参数, 也可以作为其结果, 还可以根据函数类型这一特性生成闭包
83 |
84 | 一个函数的声明通常包括关键字 `func`、函数名、分别由圆括号包裹的参数列表和结果列表, 以及由花括号包裹的函数体:
85 | ``` Go
86 | func doSomething(parameter int)(int, error) {
87 | // do something
88 | }
89 | ```
90 |
91 | 函数可以没有参数列表, 也可以没有结果列表, 但空参数列表必须保留括号, 而空结果列表则不用:
92 | ``` Go
93 | func doSomething() {
94 | // do something
95 | }
96 | ```
97 |
98 | 另外, 参数列表中的参数必须有名称, 而结果列表中结果的名称则可有可无, 不过结果列表中的结果要么全都省略名称, 要么就全都有名称, 带有结果名称的 `doSomething` 函数的声明:
99 | ``` Go
100 | func doSomething(parameter int)(result int, err error) {
101 | // 如果函数的结果有名称, 以他们为名的变量就会被隐式声明, 在这里就可以直接使用
102 | if parameter == 0 {
103 | err = errors.New("parameter is 0")
104 | return
105 | }
106 | // 给代表结果的变量赋值, 就相当于设置函数的返回结果
107 | result = parameter * 10
108 | return
109 | }
110 | ```
111 | > Go编程有一个惯用法, 即把 `error` 类型的结果作为函数结果列表的最后一员
112 |
113 | 可以在使用的时候实现函数执行体, 此时需要将函数提升成为一个类型, 调用的时候将函数当成参数传入(也叫闭包)
114 |
115 | ``` Go
116 | // 定义函数类型
117 | type funcType func (parameter1 int, parameter2 int) (result int, err error)
118 |
119 | func callFuncType (a int, b int, func_type funcType) (result int, err error) {
120 | if func_type == nil {
121 | err = errors.New("func_type is nil")
122 | return
123 | }
124 |
125 | return func_type(a, b)
126 | }
127 | ```
128 |
129 | 上面函数的使用方法:
130 | ``` Go
131 | var func_type funcType = func (parameter1 int, parameter2 int) (result int, err error) {
132 | // 这里可以定义函数执行体内的逻辑
133 | result = parameter1 + parameter2
134 | return
135 | }
136 |
137 | var result, err = callFuncType(1, 2, func_type)
138 | ```
139 |
140 | 作为一等类型的函数类型让程序的灵活性大大增加, 接口不再是定义行为的唯一途径
141 |
142 | > 函数类型的零值是 `nil`, 检查函数值是否为 `nil` 是有必要的
143 |
144 | 方法是函数的一种, 它实际上就是某个数据类型关联在一起的函数:
145 | ``` Go
146 | type myInt int
147 |
148 | func (x myInt) add(y int) myInt {
149 | x = x + myInt(y)
150 | return x
151 | }
152 | ```
153 |
154 | 从声明上看, 方法只是在关键字 `func` 和函数名称之间, 加了一个由圆括号包裹的接收者声明. 接收者声明由两部分组成:
155 | - `x` 指定类型的值在当前方法中的标识符
156 | - `myInt` 表明这个方法与哪个类型关联
157 |
158 | > 参数是以数据值传入到方法中, 修改参数 `x` 并不会影响原值(这里指调用者)
159 | ``` Go
160 | x := myInt(1)
161 | y := x.add(2)
162 | fmt.Println(x, y) // 输出: 1 3
163 | ```
164 |
165 | 值方法接收者类型是非指针的数据类型, 若将方法改为指针方法, 如下:
166 | ``` Go
167 | func (x *myInt) add(y int) myInt {
168 | *x = *x + myInt(y)
169 | return *x
170 | }
171 | ```
172 | 此时 `fmt.Println(x, y)` 的输出为 `3 3`
173 |
174 | > 对于某个非指针的数据类型, 与它关联的方法的集合中只包含它的值方法. 而对于它的指针类型, 其方法集合中既包含值方法也包含指针方法. 不过, 在非指针数据类型的值上, 也是能够调用其指针方法的, 因为Go在内部做了自动转换. 例如, 若 `add` 方法是指针方法, 那么表达式 `x.add(2)` 会被自动转换为 `(&x).add(2)`
175 |
176 | ### 参数可变型函数
177 |
178 | 参数可变型函数可以使用任何数量的尾随参数, 例如我们使用过的 `fmt.Println()`
179 | ``` Go
180 | package main
181 |
182 | import "fmt"
183 |
184 | // Here's a function that will take an arbitrary number
185 | // of `int`s as arguments.
186 | func sum(nums ...int) {
187 | fmt.Print(nums, " ")
188 | total := 0
189 | for _, num := range nums {
190 | total += num
191 | }
192 | fmt.Println(total)
193 | }
194 |
195 | func main() {
196 |
197 | // Variadic functions can be called in the usual way
198 | // with individual arguments.
199 | sum(1, 2)
200 | sum(1, 2, 3)
201 |
202 | // If you already have multiple args in a slice,
203 | // apply them to a variadic function using
204 | // `func(slice...)` like this.
205 | nums := []int{1, 2, 3, 4}
206 | sum(nums...)
207 | }
208 | ```
209 | > 如果切片中已经有多个 `args`, 请使用 `func(slice...)` 这样的方法将他们应用于参数可变型函数
210 |
211 | ## 接口
212 |
213 | Go的接口类型用于定义一组行为, 其中每个行为都由一个方法声明表示. 接口类型中的方法声明只有方法签名而没有方法体, 而方法签名包括且仅包括方法的名称、参数列表和结果列表
214 | ``` Go
215 | type Talk interface {
216 | Hello (userName string) string
217 | Talk (heard string) (saying string, end bool, err error)
218 | }
219 | ```
220 | type、接口类型名称、interface以及由花括号包裹的方法声明集合, 共同组成了一个接口类型声明
221 | > 其中每个方法声明必须独占一行
222 |
223 | 只要一个数据类型的方法集合中包含 `Talk` 接口声明的所有方法, 那么它就一定是 `Talk` 接口的实现类型, 这种接口实现方式完全是 `非入侵式` 的
224 | ``` Go
225 | type myTalk string
226 |
227 | func (talk *myTalk) Hello (userName string) string {
228 | // 省略部分代码
229 | }
230 |
231 | func (talk myTalk) Talk (heard string) (saying string, end bool, err error) {
232 | // 省略部分代码
233 | }
234 | ```
235 | > 与 `myTalk` 关联的所有方法均为指针方法, 意味着 `myTalk` 类型并不是 `Talk` 接口的实现类型, `*myTalk` 才是
236 |
237 | 一个接口类型的变量可以被赋予任何实现类型的值, 例如: `var talk Talk = new(myTalk)`, 内建函数 `new` 的功能是创建一个指定类型的值, 并返回该值的指针. 若想确定变量 `talk` 中的值是否属于 `*myTalk` 类型, 则可以用类型断言来判断: `_, ok := talk.(*myTalk)`
238 |
239 | Go的数据类型之间并不存在继承关系, 接口类型之间也是如此, 不过, 一个接口类型的声明中可以嵌入任意其他接口类型. 更通俗地讲, 一组行为中可以包含其他的行为组, 而且数量不限
240 | ``` Go
241 | type ChatBot interface {
242 | Hello2 (userName) string
243 | Talk
244 | }
245 |
246 | // 使用ChatBot
247 | var chat ChatBot = new(myTalk)
248 | chat.Hello2("lisi")
249 | chat.Hello("zhangsan")
250 | ```
251 |
252 | ## 结构体
253 |
254 | 结构体类型不仅可以关联方法, 而且可以有内置元素(又称字段), 结构体类型的声明一般以关键字 `type` 开始, 并依次包含类型名称、关键字 `struct` 以及由花括号包裹的字段声明列表
255 | ``` Go
256 | type simpleCN struct {
257 | name string
258 | talk Talk
259 | }
260 | ```
261 | 结构体类型中的每个字段声明都需独占一行, 一般情况下, 字段声明需由字段名称和字段类型的字面量组成
262 |
263 | 结构体类型的值一般由复合字面量来表达, 复合字面量可以由类型字面量和由花括号包裹的键值对列表组成, 在同一个结构体字面量中, 一个字段名称只能出现一次
264 |
265 | 可以忽略字段的名称, 但是有两个限制:
266 | - 要么忽略所有字段名称, 要么都不忽略
267 | - 多个字段值的顺序应该与结构体类型中字段声明的顺序一致, 并且不能够省略对任何一字段的赋值. 例如 `simpleCN{"simple.cn", nil}` 是合法的, 而 `simpleCN{nil, "simple.cn"}` 和 `simpleCN{"simple.cn"}` 就不合法. 在不忽略字段名称的写法中, 未被赋值的字段会自动被其类型的零值填充
268 |
269 | > 与数组类型相同, 结构体类型属于值类型, 因此结构体类型的零值不是 `nil`, 例如 `simpleCN` 的零值就是 `simpleCN{}`
270 |
271 | 结构体的匿名字段:
272 | ``` Go
273 | type User struct {
274 | name string
275 | }
276 |
277 | type Team struct {
278 | User // 匿名字段, 那么默认Team就包含了User的所有字段, 也包含User的所有method方法, 即: 字段/方法继承
279 | name string // 假设有字段名冲突, 访问Team.name也只是Team的name字段, 如需访问User的name需要Team. User.name, 即: 字段/方法覆盖
280 | }
281 | ```
282 |
--------------------------------------------------------------------------------
/Note/Grammar-rules/Flow-control.md:
--------------------------------------------------------------------------------
1 | # Go流程控制
2 |
3 | Go在流程控制方面的特点如下:
4 | - 没有 `do` 和 `while` 循环, 只有一个更广义的 `for` 语句
5 | - `switch` 语句灵活多变, 还可以用于类型判断
6 | - `if` 语句和 `switch` 语句都可以包含一条初始化子语句
7 | - `break` 语句和 `continue` 语句可以后跟一条标签(`label`)语句, 以标识需要终止或继续的代码块
8 | - `defer` 语句可以使用我们更加方便地执行异常捕获和资源回收任务
9 | - `select` 语句也可用于多分支选择, 但只与通道配合使用
10 | - `go` 语句用于异步启用 `goroutine` 并执行指定函数
11 |
12 | ## 代码块和作用域
13 |
14 | 代码块就是一个由花括号包裹的表达式和语句的序列, 代码块中也可以不包含任何内容, 即: 空代码块`{}`
15 |
16 | 除了显式的代码块之外, 还有一些隐式的代码块:
17 | - 所有Go代码形成了一个最大的代码块, 即: 全域代码块
18 | - 每一个代码包中的代码共同组成了一个代码块, 即: 代码包代码块
19 | - 每一个源码文件都是一个代码块, 即: 源码文件代码块
20 | - 每一个 `if`、 `for`、 `switch` 和 `select` 语句都是一个代码块
21 | - 每一个在 `switch` 或 `select` 语句中的 `case` 分支都是一个代码块
22 |
23 | 在Go中, 使用代码块表示词法上的作用域范围, 具体规则如下:
24 | - 一个预定义标识符的作用域是全域代码块
25 | - 表示一个常量、变量、类型或函数(不包括方法), 且声明在函数之外的标识符的作用域是当前的代码包代码块
26 | - 被导入的代码包的名称的作用域是当前的源码文件代码块
27 | - 表示方法接收者、方法参数或方法结果的标识符的作用域是当前的方法代码块
28 | - 对于表示常量、变量、类型或函数的标识符, 如果被声明在函数内部, 那么作用域就是包含其声明的那个最内层的代码块
29 |
30 | 此外, 我们还可以重新声明已经在外层代码块中声明过的标识符, 当在内层代码块中使用这个标识符, 它表示的总是在该代码块中与它绑定在一起的那个程序实体, 可以说, 此时在外层代码块中声明的那个同名标识符被屏蔽了
31 |
32 | ## if 语句
33 |
34 | `if` 语句会根据条件表达式来执行两个分支中的一个, 如果表达式的结果是 `true` 则执行 `if` 分支, 否则 `else` 分支会被执行
35 | ``` Go
36 | var number int
37 | // 没有else分支
38 | if number < 100 {
39 | number++
40 | }
41 |
42 | // 有else分支
43 | if number < 100 {
44 | number++
45 | } else {
46 | number--
47 | }
48 |
49 | // 包含初始化子语句, 用于初始化局部变量
50 | if diff := 100 - number; number < 100 {
51 | number++
52 | } else {
53 | number--
54 | }
55 |
56 | // 支持串联
57 | if diff := 100 - number; number < 100 {
58 | number++
59 | } else if number < 200 {
60 | number--
61 | } else {
62 | number -= 100
63 | }
64 | ```
65 |
66 | ## switch 语句
67 |
68 | `switch` 语句也提供了一种多分支执行的方法, 它会用一个表达式或类型说明符与每一个 `case` 进行比较, 并决定执行哪一个分支
69 |
70 | ### 表达式 switch 语句
71 |
72 | 在表达式 `switch` 语句中, `switch` 表达式和所有 `case` 携带的表达式(也称为 `case` 表达式)都会被求值, 并且执行顺序是自左向右, 自上而下, 只有第一个与 `switch` 表达式的求值结果相同的 `case` 表达式的分支会执行, 如果没有找到匹配的 `case` 表达式并且存在 `default case`, 那么 `default case` 的分支会执行
73 | > `default case` 最多只能有一个, 另外 `switch` 表达式可以省略, 这时 `true` 会作为(并不存在的) `switch` 表达式的结果
74 | ``` Go
75 | var content string = "Go"
76 |
77 | switch content {
78 | default:
79 | fmt.Printf("default\n")
80 | case "Python":
81 | fmt.Printf("Python\n")
82 | case "Go": {
83 | fmt.Printf("Go")
84 | fmt.Printf("\n")
85 | }
86 | }
87 |
88 | // switch语句可以包含一条子语句来初始化局部变量
89 | switch lang := strings.TrimSpace(content); lang {
90 | default:
91 | fmt.Printf("default\n")
92 | case "Python":
93 | fmt.Printf("Python\n")
94 | case "Go":
95 | fmt.Printf("Go\n")
96 | }
97 |
98 | // 使用fallthrough强制执行后面的case代码, fallthrough不会判断下一条case的结果(或default)是否为true
99 | switch lang := strings.TrimSpace(content); lang {
100 | case "Python":
101 | fmt.Printf("Python\n")
102 | fallthrough
103 | case "Go":
104 | fmt.Printf("Go\n")
105 | case "Java", "C":
106 | fmt.Printf("Other\n")
107 | default:
108 | fmt.Printf("default\n")
109 | }
110 | ```
111 | > `break` 可以跳出当前 `switch` 语句
112 |
113 | ### 类型 switch 语句
114 |
115 | 类型 `switch` 语句将对类型进行判断, 而不是值
116 | ``` Go
117 | var v interface{}
118 | // do something
119 | switch v.(type) {
120 | case string:
121 | fmt.Printf("string value = %s\n", v)
122 | case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:
123 | fmt.Printf("integer value = %d\n", v)
124 | default:
125 | fmt.Printf("unsupported value, type = %T\n", v)
126 | }
127 | ```
128 | 类型 `switch` 语句的 `switch` 表达式会包含一个特殊的类型断言, 例如 `v.(type)`, 它虽然特殊, 但是也要遵循类型断言的规则; 其次, 每个 `case` 表达式中包含的都是类型字面量而不是表达式; `fallthrough` 语句不允许出现在类型 `switch` 语句中
129 |
130 | 类型 `switch` 语句的 `switch` 表达式还有一种变形写法, 如下:
131 | ``` Go
132 | switch i := v.(type) {
133 | case string:
134 | fmt.Printf("string value = %s\n", i)
135 | case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:
136 | fmt.Printf("integer value = %d\n", i)
137 | default:
138 | fmt.Printf("unsupported value, type = %T\n", i)
139 | }
140 | ```
141 | 这里的 `i := v.(type)` 使经类型转换后的值得以保存, `i` 的类型一定会是 `v` 的值的实际类型
142 |
143 | ## for 语句
144 |
145 | `for` 语句用于根据给定的条件重复执行一个代码块, 这个条件或由 `for` 子句直接给出, 或从 `range` 子句中获得
146 |
147 | ### for 子句
148 |
149 | 一条 `for` 语句可以携带一条 `for` 子句, `for` 子句可以包含初始化子句、条件子句和后置子句
150 | ``` Go
151 | package main
152 |
153 | import "fmt"
154 |
155 | func main() {
156 |
157 | // The most basic type, with a single condition.
158 | i := 1
159 | for i <= 3 {
160 | fmt.Println(i)
161 | i = i + 1
162 | }
163 |
164 | // A classic initial/condition/after `for` loop.
165 | for j := 7; j <= 9; j++ {
166 | fmt.Println(j)
167 | }
168 |
169 | // `for` without a condition will loop repeatedly
170 | // until you `break` out of the loop or `return` from
171 | // the enclosing function.
172 | for {
173 | fmt.Println("loop")
174 | break
175 | }
176 |
177 | // You can also `continue` to the next iteration of
178 | // the loop.
179 | for n := 0; n <= 5; n++ {
180 | if n%2 == 0 {
181 | continue
182 | }
183 | fmt.Println(n)
184 | }
185 | }
186 | ```
187 | 在 `for` 子句的初始化子句和后置子句同时被省略, 或者其中的部分代码都省略的情况下, 分隔符 `;` 可以省略:
188 | ``` Go
189 | var m = 1
190 | for m < 50 { 省略初始化子句和后置子句
191 | m *= 3
192 | }
193 | ```
194 |
195 | ### range 子句
196 |
197 | 一条 `for` 语句可以携带一条 `range` 子句, 这样就可以迭代出一个数组或切片值中的每个元素、一个字符串中的每个字符, 或者一个字典中的每个键值对, 以及持续地接收一个通道类型值中的元素, 随着迭代的进行, 每一次获取的迭代值(索引、元素、字符或键值对)都会赋值给相应的迭代变量:
198 | ``` Go
199 | package main
200 |
201 | import "fmt"
202 |
203 | func main() {
204 |
205 | // Here we use `range` to sum the numbers in a slice.
206 | // Arrays work like this too.
207 | nums := []int{2, 3, 4}
208 | sum := 0
209 | for _, num := range nums {
210 | sum += num
211 | }
212 | fmt.Println("sum:", sum)
213 |
214 | // `range` on arrays and slices provides both the
215 | // index and value for each entry. Above we didn't
216 | // need the index, so we ignored it with the
217 | // blank identifier `_`. Sometimes we actually want
218 | // the indexes though.
219 | for i, num := range nums {
220 | if num == 3 {
221 | fmt.Println("index:", i)
222 | }
223 | }
224 |
225 | // `range` on map iterates over key/value pairs.
226 | kvs := map[string]string{"a": "apple", "b": "banana"}
227 | for k, v := range kvs {
228 | fmt.Printf("%s -> %s\n", k, v)
229 | }
230 |
231 | // `range` can also iterate over just the keys of a map.
232 | for k := range kvs {
233 | fmt.Println("key:", k)
234 | }
235 |
236 | // `range` on strings iterates over Unicode code
237 | // points. The first value is the starting byte index
238 | // of the `rune` and the second the `rune` itself.
239 | for i, c := range "go" {
240 | fmt.Println(i, c)
241 | }
242 | }
243 | ```
244 | > 在 `range` 关键字右边的是 `range` 表达式, `range` 表达式一般只会在迭代开始前被求值一次
245 |
246 | 使用 `range` 子句, 有以下三点需要注意:
247 | - 若对数组、切片或字符串进行迭代, 且 `:=` 左边只有一个迭代变量时, 一定要小心, 这时只会得到其中元素的索引, 而不是元素本身; 这很可能不是你想要的
248 | - 迭代没有任何元素的数组值、为 `nil` 的切片值, 为 `nil` 的字典或为 `""` 的字符串值, 并不会执行 `for` 语句中的代码, `for` 语句在一开始时就会直接结束执行, 因为这些值的长度都为 `0`
249 | - 迭代为 `nil` 的通道值会让当前流程永远阻塞在 `for` 语句上!
250 |
251 | ## defer 语句
252 |
253 | `defer` 语句用于延迟操作, 用于函数体内, 通常用于函数结束后的一系列操作, 例如清理操作
254 |
255 | 有几点需要注意:
256 | - 如果 `defer` 函数中使用外部变量, 应通过参数传入
257 | - 调用 `defer` 函数时若有参数传入, 那么将在 `defer` 执行时求值
258 | - 多个 `defer` 函数调动时, 会与调用顺序相反, 可以想象为将调用压入同一栈, 调用时从栈顶取出
259 |
260 | ``` Go
261 | func printNumbers() {
262 | for i := 0; i < 5; i++ {
263 | defer func(n int) {
264 | fmt.Printf("%d", n)
265 | }(i * 2)
266 | }
267 | }
268 | // 执行结果: 86420
269 | ```
270 |
271 | ## panic 和 recover
272 |
273 | 通常情况下, 函数向其调用方法报告的错误都是返回一个 `error` 类型的值, 但是遇到致命的轻局昂将会导致程序无法继续运行, Go推荐通过调用 `panic` 函数来报告错误
274 |
275 | ### panic
276 |
277 | 用于停止当前的控制流程并引发一个运行时恐慌, 可以接受一个任意类型的参数值, 不过这个参数值的类型通常是 `string` 或 `error`, 因为这样更容易描述运行时恐慌的信息:
278 | ``` Go
279 | func main() {
280 | panic("a problem")
281 | }
282 |
283 | // 也可以通过Go的运行时系统来引发
284 | fmt.Println([3]int{1, 2, 3}[3]) // 数组越界, 相当于调用panic并传入一个runtime.Error类型的参数值
285 | ```
286 | > `runtime.Error` 是一个接口类型, 并且内嵌了Go内置的error接口类型
287 |
288 | ### recover
289 |
290 | 运行时恐慌一旦被引发, 就会向调用方传播直至程序崩溃, `recover` 用于“拦截”运行时恐慌的内建函数, 它可以使当前的程序从恐慌状态中恢复并重新获得流程控制权, `recover` 函数被调用后, 会返回一个 `interface{}`类型的结果, 如果当时的程序正处于运行时恐慌的状态, 那么这个结果就会是非 `nil` 的
291 | > `recover` 函数应该与 `defer` 语句搭配起来使用:
292 | ``` Go
293 | defer func() {
294 | if p := recover(); p != nil {
295 | fmt.Printf("Recovered panic: %s\n", p)
296 | }
297 | }()
298 | ```
299 | 将此段代码放在函数体的开始处, 这样可以有效防止该函数及其下层调用中的代码引发运行时恐慌, 那么这个结果就会是非 `nil`, 一旦发现其结果非 `nil`, 就应该采取相应的措施
300 |
301 | Go标准库中有一种常见的用法值得参考:
302 | ``` Go
303 | // fmt中的Token函数部分声明
304 | func (s *ss) Token(skipSpace bool, f func(rune) bool) (tok []byte, err error) {
305 | defer func() {
306 | if e := recover(); e != nil {
307 | if se, ok := e.(scanError); ok {
308 | err = se.err
309 | } else {
310 | panic(e)
311 | }
312 | }
313 | }()
314 | // 省略部分代码
315 | }
316 | ```
317 | 在 `Token` 函数包含的延迟函数中, 当运行时恐慌携带值的类型是 `fmt.scanError` 时, 这个值就会被赋值给代表结果值的变量 `err`, 否则运行时恐慌就会被重新引发. 如果这个重新引发的运行时恐慌传递到了调用栈的最顶层, 那么标准输出上就会打印出类型这样的内容:
318 | ```
319 | panic: <运行时恐慌被首次引发时携带的值的字符串形式> [recovered]
320 | panic: <运行时恐慌被重新引发时携带的值的字符串形式>
321 |
322 | goroutine 1 [running]:
323 | main.func·001()
324 | <调用栈信息>
325 |
326 | goroutine 2 [runnable]:
327 | exit status 2
328 | ```
329 | 这里展现的惯用法与两个:
330 | - 可以把运行时恐慌的携带值转换为 `error` 类型值, 并当做常规结果返回给调用方, 这样既阻止了运行时恐慌的扩散, 又传递了引起恐慌的原因
331 | - 检查运行时恐慌携带值的类型, 并根据类型做不同的后续动作, 这样可以精确地控制程序的错误处理行为
--------------------------------------------------------------------------------