A Tour Of Go
A Tour Of Go
原文地址:https://tour.golang.org/welcome/1
在导入 “fmt” 包(Package)后, 可使用 fmt.Println 和 fmt.Printf 函数打印结果, fmt.Println 接收若干参数, 并在一行按参数类型依次打印, fmt.Printf 采用格式控制字符串的方式, %T 用来打印变量的类型, %v 用来打印一个变量的值, %q 给字符串加上引号
第一部分 包、变量和函数
1.1 包和符号
导入路径
导入路径示例: “math/rand”, 按约定, 包名应跟导入路径的最后一个元素相同
两种import方法
1 | import ( |
1 | import "fmt" |
第一种更好
符号
名字首字母大写意味着导出符号
1.2 函数和参数
定义函数
1 | func add(x int, y int) int { |
参数的两种写法
1 | x int, y int |
1 | x, y int |
多值返回
1 | func swap(x, y string) (string, string) { |
命名返回值
1 | func split(sum int) (x, y int) { |
1.3 数据类型和变量
可以在包、函数体作用域定义变量
1 | var c, python, java bool |
初始化
1 | var i, j int = 1, 2 |
相同类型可只在最后声明一次
短赋值语法
1 | k := 3 |
省略了 var 关键字和 type declaration, 只能在函数体作用域使用
基本数据类型
1 | bool |
复数的表示方法: -5 + 12i
分块定义变量
1 | var ( |
默认值
未初始化的变量将获得默认值
1 | 0 false "" |
类型转换
1 | T(v) |
Golang没有隐式转换
类型推断
1 | := syntax |
示例:1
2
3// v1 和 v2 的类型都是 int
v1 := 42
var v2 = 42
常量
1 | const Pi = 3.14 |
不能使用 := 语法定义常量
理解: https://tour.golang.org/basics/16
第二部分 流程控制 for, if, else, switch and defer
2.1 for
1 | for i := 0; i < 10; i++ { |
Golang 只有这一种循环, 用法与 c 一模一样, 语法省略了小括号, 且花括号是必须的
可省略第一、三条表达式, 分号也可省略
1 | for sum < 1000 { |
无限循环
1 | for { |
2.2 if
if, 与 c 相比, 语法省略了小括号, 且花括号是必须的
另一种形式
1 | if init statement; condition expression { |
在 init statement 定义的变量同时在 if 和 else 的作用域内有效
练习:实现Sqrt函数
https://tour.golang.org/flowcontrol/8
1 | package main |
2.3 switch
Golang 的 switch 语句不需要 break 关键字(只执行匹配 condition expression 的那一个 case), case 后的表达式没有常量、整型的限制
switch 也有一个可选的 init statement
求值顺序
Golang 按照 case 定义顺序从上到下求值, 一旦某 case 后的 expression 的值满足 switch 的 condition expression, 停止求值, 转而执行该 case
switch true
switch 可省略 condition expression 不写, 相当于 switch true, case 后就应跟 bool 表达式
2.4 defer
defer 关键字推迟一个函数调用至所属函数 return 语句之后执行, 对该函数参数的求值立即执行, 真正调用它则在调用它的函数执行完 return 语句之后
1 | package main |
打印
1 | hello |
被推延的函数是 push 到栈上的, 调用它们的函数返回之前, 被推延函数按照先进后出的方式执行
第三部分 结构、切片和图
3.1 指针
*T 是指向 T 类型的指针(Pointer)类型, 指针类型有空值 nil
取地址运算符是 &, 解引用运算符是 *
Golang 不支持指针运算
3.2 结构(Struct)
定义一个结构
1 | type Vertex struct { |
字面量
变量名后面的值在 Golang 中叫做字面量(Literal), 你可以直接使用一个字面量, 也可以赋值给一个变量再使用
初始化结构
1 | // Vertex{1, 2}, Vertex{X: 1}, Vertex{1}都是结构字面量 |
访问结构成员
1 | v := Vertex{1, 2} |
结构指针
1 | 设 p := &v |
3.3 数组
数组(Array)也是一种类型, [n]T 是一个元素类型为 T 容量为 n 的数组类型, 数组的大小不可调整
1 | // 定义一个容量为 10 的 int 型数组 |
数组初始化
1 | primes := [6]int{2, 3, 5, 7, 11, 13} |
3.4 切片
切片(Slice)是数组上大小可变, 灵活的视图, []T 是一个元素类型为 T 的切片类型, 指明两个下标来创建一个切片, 一个下限, 一个上限, [low:high]选中一个半开半闭区间, 该区间包含元素[low], 不包含元素[high]
切片初始化
1 | // 以下语句创建了一个匿名数组, 以及名为 s 的视图(切片)引用它 |
默认下标
在创建一个切片时, 可省略下标不写, Golang 自动填入默认值, 下限为 0, 上限为数组大小
切片的长度、容量
切片的长度是切片选中的元素数量, 切片的容量是切片引用的数组从切片第一个元素开始到数组最后一个元素的元素数量, 使用len(s)来得到切片长度、cap(s)来得到切片容量, 切片的长度是可以调整的, 语法为: s=s[low:high]
空切片
可以创建一个空切片, 它的值为 nil, 空切片的长度和容量都为0, 不引用任何数组
1 | // 创建一个空切片 |
make函数
make内建函数动态创建一个指定容量的空白(所有内存单元清零)数组, 返回引用它的切片
它有两个参数的版本: make(type, len)1
a := make([]int, 5) // len(a)=5, cap(b)=5
和三个参数的版本: make(type, len, cap)1
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
追加
调用 append 函数来向一个切片追加数据, append 函数有如下形式
1 | func append(s []T, vs ...T) []T |
该函数返回值是一个包括参数 s 里的所有元素以及所有追加的新元素的切片, 如果 s 的容量不能容纳总的元素, append 会申请一个更大的数组, 并返回引用它的切片
for的range形式
for 的 range 形式依次迭代一个切片或图, 如果迭代的对象是一个切片, 那么每次迭代都获得两个值, 切片中的下标 i, 以及切片中该下标处的值的拷贝
语法如下:1
2
3for i, v := range []int{1, 2, 4, 8, 16, 32, 64, 128} {
fmt.Printf("2**%d = %d\n", i, v)
}
你可以把下标或值的拷贝赋值给 _
来忽略它(用不到的值应该赋值给 _
, 否则会报错), 如果在循环中用不到切片的值, 可以省略上例中整个 , v
不写
练习:实现Pic
https://tour.golang.org/moretypes/18
1 | func Pic(dx, dy int) [][]uint8 { |
3.5 图
图(Map)存储键到值的映射, 图的类型声明为 map[K]V , 其中, K 是键的类型, V 是值的类型, 图有空值 nil
1 | type Vertex struct { |
图的make函数
图的 make 函数创建一个指定类型的图, 等待使用, 图的 make 函数有如下形式
1 | // 不确定 |
访问键-值对
1 | m["Bell Labs"] = Vertex{ |
图的字面量
1 | // 注意末尾的逗号 |
字面量中值的类型(上例中 Vertex)可以省略不写
理解: https://tour.golang.org/moretypes/21
图的操作
插入或修改元素
1 | m[key] = elem |
取得元素
1 | elem = m[key] |
删除元素
1 | delete(m, key) |
利用二元赋值检查键是否存在
1 | elem, ok = m[key] |
如果键存在, ok 为 true, elem 为该键对应的值; 如果键不存在, ok 为 false, elem 为对应类型的默认值(零值或空值)
练习:实现WordCount
https://tour.golang.org/moretypes/23
1 | package main |
3.6 函数
函数量(Function value)
函数本身也是一种量, 可作为参数传递或作为返回值返回
1 | // func(float64, float64) float64就是函数的类型 |
闭包
Golang 支持闭包(Closure), 闭包是一个引用外层变量(不在函数体内定义的变量)的函数量, 在下面的例子中, adder
函数就返回一个闭包, 每个闭包跟它自己的外层变量 sum
关联
1 | func adder() func(int) int { |
练习:斐波那契闭包
1 | package main |
用 defer
消掉 t
1 | func fibonacci() func() int { |
第四部分、方法和接口
4.1 方法
Golang 允许你为一个类型定义方法(Method), 方法是带有接收器(Receiver)参数的函数, 接收器参数出现在函数定义命令 func
和函数名中间
以下示例定义了一个接收器为 v Vertex
的方法
1 | type Vertex struct { |
调用该方法
1 | v := Vertex{3, 4} |
你只能为在本包中定义的类型定义方法, 以下示例为基本数据类型 float64
自定义方法 abs
1 | type MyFloat float64 |
指针接收器
指针接收器(Pointer Receiver)是接收器类型为指针类型(一级指针)的接收器, 只有拥有指针接收器的方法才可以修改调用该方法的对象, 因此, 指针接收器比值接收器(Value Receiver)更常见
值得注意的是, 为了使用方便, 你并不一定在一个地址上调用一个有指针接收器的方法, Golang 会自动将 v.Scale(5) 译成(看成) (&v).Scale(5)
如果方法的接收器是一个值类型, Golang 还会将 p.Scale(5) 译成 (*p).Scale(5), 其中, p = &v
综上所述, 调用方法在语法上不需要关注接收器是值类型还是指针类型
4.2 接口
接口第一是一种类型, 其次, 它是一个方法签名(Signature)的集合, 任何实现了这些方法的对象都可以赋值给此接口的变量
定义接口
以下示例定义一个接口
1 | type Abser interface { |
实现接口
为 *Vertex 实现 Abser 接口
1 | func (v *Vertex) Abs() float64 { |
接口的使用
1 | v := Vertex{3, 4} |
接口值
可以认为一个接口值(接口的变量)存储的是一个具体类型和对应该类型的值的元组(value type), 调用接口的方法是用 value 去调用这个具体类型中同名的方法
如果接口的 value 是 nil, 那接口方法的实现的接收器也将是一个 nil 值, 但接口值本身不是 nil
下例中, I
是一个接口类型, T
是一个结构类型, 代码执行完, t
有值 nil, i
的 value 同样有值 nil, 但 i
本身不为 nil
1 | var i I |
如果只定义了接口变量, 而没有初始化它, 这个变量就有值 nil, 在一个 nil 接口上调用接口方法会报运行时错误
空接口
空接口声明的方法签名数为 0, 任何类型的变量都可以赋值给这个接口变量(因为任何类型都实现了至少 0 个接口方法), 空接口类型常用作函数参数以接收任意类型的变量, 如 fmt.Print
下例体现了这种用法
1 | func describe(i interface{}) { |
4.3 类型担保
类型担保可以访问一个接口的具体类型 type
1 | t := i.(T) |
该语句断言接口 i
存储的值 value
的类型是 T
, 并把 value
赋值给 t
, 如果 i
的类型不是 T
, 这条语句触发一个 panic
要测试一个接口的具体类型 type
是否为 T
, 采用二值形式的类型担保
1 | t, ok := i.(T) |
如果 value
的类型是 T
, t
有值 value
, ok
有值 true
; 如果不是, t
为对应类型 type
的默认值(零值或空值), ok
有值 false
, 不会触发 panic
类型switch
类型 switch 允许你构造一个 switch 结构进行一系列类型担保
类型 switch 的语法和常规 switch 一致, 不同之处是 case 后的表达式是一个类型(而不是值)
1 | switch v := i.(type) { |
其中, v := i.(type)
中的 type
是一个关键字, 该关键字只能出现在类型 switch 中
4.4 Stringer
Stringer
是定义在 fmt
包中的一个接口
1 | type Stringer interface { |
自定义类型应该实现此接口, String
方法返回一个字符串描述对象自身, fmt
和其他的一些包查找这个接口打印一个对象可读性更高的字符串表示
练习:Stringer接口
https://tour.golang.org/methods/18{: target=”_blank” }
1 | package main |
4.5 错误
error
也是一个内建接口
1 | type error interface { |
方法 Error
返回描述错误原因的字符串, 与 Stringer
类似, fmt
查找这个直接打印错误原因
函数通常返回 error
, 调用者应该测试返回值是否为 nil, 为 nil, 代表成功, 不为 nil, 代表失败
1 | if err := run(); err != nil { |
练习:错误
https://tour.golang.org/methods/20{: target=”_blank” }
1 | // 这个练习要用到练习:实现 Sqrt 中的算法 |
4.6 Readers
io 包定义了 Reader 接口, 你可以在 Golang 标准库找到许多 Reader 接口的实现
Reader 接口有方法 Read
1 | func (T) Read(b []byte) (n int, err error) |
Read 方法填充给定字节切片, 返回填充字节数和错误消息, 如果流中的数据已经读完, err 返回 io.EOF 值
练习:Readers
https://tour.golang.org/methods/22{: target=”_blank” }
1 | package main |
练习:rot13Reader
https://tour.golang.org/methods/23{: target=”_blank” }
这个练习应用的是一个 Reader 包装另一个 Reader 并修改流的所谓包装器模式
1 | package main |
4.7 Images
image 包定义了 Image 接口
1 | package image |
color.Model 和 color.Color 同样是接口, 不过 Golang 提供了预定义的 color.RGBAModel 和 color.RGBA 供我们使用
练习:Images
1 | // 这个练习要用到练习:实现Pic中的算法 |
第五部分、并发
5.1 Go过程
Go 过程(Goroutine)是由 Go 运行时管理的轻量级线程
使用关键字 go 开始一个 Go 过程
1 | go f(x, y, z) |
对 f, x, y 和 z 的求值发生在当前 Go 过程, 而 f 的运行则在一个新的 Go 过程
所有 Go 过程都运行在同一个地址空间, 因此, 对共享内存的访问必须同步, sync 包提供用于同步的原语
5.2 通道
通道(Channel)是一种有类型管道(typed conduit), 你可以通过通道运算符 <- 发送或接收数据
1 | ch <- v // Send v to channel ch. |
通道必须创建后才能使用
1 | ch = make(chan int) |
缺省情形下, 除非管道的另一端已准备好, 否则发送或接收操作将阻塞, 该特性使得 Go 过程可以不使用显式锁或条件变量进行同步
下例并行对切片中的元素求和, 主函数 main 收集两个 Go 过程计算出的部分和, 相加得到最终结果
1 | package main |
通道的缓存
可以设置一个通道的缓存大小, 以下语句初始化一个缓存容量为 100 的通道(Buffered Channel)
1 | ch = make(chan int, 100) |
只有当通道缓存已满时, 向通道的发送操作才会阻塞, 当且仅当通道缓存为空时, 从通道接收的操作才会阻塞
关闭
Golang 允许发送端关闭一个通道来通知接收者已经没有数据待发送
1 | close(ch) |
接收端使用二元形式的接收表达式来测试通道是否已经关闭
1 | v, ok = <- ch |
如果通道已关闭, ok 有值 false, 循环 for v := range ch
连续从通道 ch 接收数据直到 ch 被关闭
一般来说, 应该由发送端来关闭通道, 而不是接收端, 向一个已关闭的通道发送数据将触发 panic. 另外, 关闭操作不是强制的
5.3 Select
select 语句阻塞一个 Go 过程以等待若干通信操作, 如果 select 语句的所有 case 都阻塞, select 也阻塞; 如果有一个 case 准备好, select 执行那一个准备好的 case; 如果同时有一个以上的 case 准备好, select 随机选择一个执行; 如果没有一个 case 阻塞, select 运行完毕
1 | package main |
default关键字
default 关键字规定当一个 select 语句的所有 case 都阻塞时执行的操作
1 | select { |
5.4 练习:相等二叉树
https://tour.golang.org/concurrency/9{: target=”_blank” }
1 | package main |
5.5 sync.Mutex
Golang 标准库定义了互斥体 sync.Mutex 以及它的两个方法 Lock 和 Unlock
用 Lock 和 Unlock 包围一个代码块能够确保这段代码总是在互斥地执行, 同时, 利用好 defer 关键字可以确保离开函数后 mutex 已经释放
5.6 练习:网络爬虫
https://tour.golang.org/concurrency/10{: target=”_blank” }
1 | package main |