摘自 Go 语言之旅
                目录
1. 包、变量和函数
1.1 包
- 每个 Go 程序都是由包构成的
 - 程序从
main包开始运行 - 包名与导入路径的最后一个元素一致
 
1.2 导入
import (
    // 标准库
    "fmt"
    "math"
    // 本地包
    "apiserver/model"
    "apiserver/log"
    // 外部包
    "github.com/spf13/viper"
)
1.3 导出名
导出名以大写字母开头:
package main
import (
    "fmt"
    "math"
)
func main() {
    fmt.Println(math.Pi)
}
------
3.141592653589793
1.4 函数
函数可以有多个返回值:
package main
import "fmt"
func swap(x, y string) (string, string) {
    return y, x
}
func main() {
    a, b := swap("Hello", "world")
    fmt.Println(a, b)
}
------
world Hello
也可以分别为返回值命名:
package main
import "fmt"
func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return
}
func main() {
    fmt.Println(split(26))
}
------
11 15
1.5 基本类型
Go 语言的基本类型如下:
bool
string
int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名, 表示一个 Unicode 码点
float32 float64
complex64 complex128
测试一下:
package main
import (
    "fmt"
    "math/cmplx"
)
var (
    ToBe   bool       = false
    MaxInt uint64     = 1<<64 - 1
    z      complex128 = cmplx.Sqrt(-5 + 12i)
)
func main() {
    fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
    fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
    fmt.Printf("Type: %T Value: %v\n", z, z)
}
------
Type: bool Value: false
Type: uint64 Value: 18446744073709551615
Type: complex128 Value: (2+3i)
1.6 零值
- 数值:
0 - 布尔:
false - 字符串:
"" 
package main
import "fmt"
func main() {
    var i int
    var f float64
    var b bool
    var s string
    fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
------
0 0 false ""
1.7 类型转换
表达式T(v)将值v转换为类型T:
package main
import (
    "fmt"
    "math"
)
func main() {
    var x, y int = 3, 4
    var f float64 = math.Sqrt(float64(x*x + y*y))
    var z uint = uint(f)
    fmt.Println(x, y, z)
}
------
3 4 5
1.8 类型推导
package main
import "fmt"
func main() {
    v1 := 42
    fmt.Printf("v1 is of type %T\n", v1)
    v2 := 3.1415926
    fmt.Printf("v2 is of type %T\n", v2)
    v3 := 0.867 + 0.5i
    fmt.Printf("v3 is of type %T\n", v3)
}
------
v1 is of type int
v2 is of type float64
v3 is of type complex128
1.9 常量
常量使用const关键字声明,可以是字符、字符、布尔值或数值,且不能用:=语法声明:
package main
import "fmt"
const Pi = 3.14
func main() {
    const World = "世界"
    fmt.Println("Hello", World)
    fmt.Println("Happy", Pi, "Day")
    const Truth = true
    fmt.Println("Go rules?", Truth)
}
------
Hello 世界
Happy 3.14 Day
Go rules? true
1.10 数值常量
数值常量是高精度的值,一个未指定类型的常量由上下文来决定其类型:
package main
import "fmt"
const (
    // 将 1 左移 100 位来创建一个非常大的数字
    // 即这个数的二进制是 1 后面跟着 100 个 0
    Big = 1 << 100
    // 再往右移 99 位,即 Small = 1 << 1,或者说 Small = 2
    Small = Big >> 99
)
func needInt(x int) int { 
    return x * 10 + 1 
}
func needFloat(x float64) float64 {
    return x * 0.1
}
func main() {
    fmt.Println(needInt(Small))
    fmt.Println(needFloat(Small))
    fmt.Println(needFloat(Big))
}
------
21
0.2
1.2676506002282295e+29
2. 流程控制语句
2.1 for 循环
for i := 0; i < 10; i++ {
    sum += i
}
省略初始语句及后置语句,可用作while:
for sum < 1000 {
    sum += sum
}
无限循环:
for {
    // do something
}
2.2 if else 语句
if v := math.Pow(x, n); v <lim {
    return v
} else {
    fmt.Printf("%g >= %g\n", v, lim)
}
return lim
2.3 switch 分支
switch的case语句从上到下顺次执行,直到匹配成功时停止:
package main
import (
    "fmt"
    "runtime"
)
func main() {
    fmt.Print("Go runs on ")
    switch os := runtime.GOOS; os {
    case "windows":
        fmt.Println("Windows.")
    case "darwin":
        fmt.Println("OS X.")
    case "linux":
        fmt.Println("Linux.")
    default:
        fmt.Printf("%s.\n", os)
    }
}
没有条件的switch同switch true一样,可以将一长串的if-else写得更清晰:
package main
import (
    "fmt"
    "time"
)
func main() {
    t := time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("Good morning!")
    case t.Hour() < 17:
        fmt.Println("Good afternoon!")
    default:
        fmt.Println("Good evening!")
    }
}
2.4 defer 栈
defer语句会将函数推迟到外层函数返回之后执行:
推迟调用的函数其参数会立即求值,但直到外层函数返回之前,该函数都不会被调用
package main
import "fmt"
func main() {
    defer fmt.Println("world")
    fmt.Println("Hello")
}
------
Hello
world
推迟的函数调用会被压入栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用:
package main
import "fmt"
func main() {
    fmt.Println("counting")
    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }
    fmt.Println("done")
}
------
counting
done
9
8
7
6
5
4
3
2
1
0
3. struct、slice、map
3.1 指针
指针保存了值的内存地址,类型*T是指向T类型值的指针,其零值为nil:
var p *int
fmt.Println(p)
------
0xc000062070<nil>
与 C 不同,Go 没有指针运算。
package main
import "fmt"
func main() {
    i, j := 42, 2701
    p := &i         // 指向 i
    fmt.Println(*p) // 通过指针读取 i 的值
    *p = 21         // 通过指针设置 i 的值
    fmt.Println(i)  // 查看 i 的值
    p = &j         // 指向
    *p = *p / 37   // 通过指针对 j 进行除法运算
    fmt.Println(j) // 查看 j 的值
    fmt.Print(p)   // 查看 j 的地址
}
------
42
21
73
0xc000062070
3.2 结构体
一个结构体struct就是一组字段field。当有一个指向结构体的指针p时,Go 允许使用隐式间接引用,直接通过p.X访问其字段:
package main
import "fmt"
type Vertex struct {
    X int
    Y int
}
func main() {
    v := Vertex{
        X: 1,
        Y: 2,
    }
    p := &v
    p.X = 1e9
    fmt.Println(v)
}
------
{1000000000 2}
结构体文法通过直接列出字段的值来新分配一个结构体,使用Name:语法可以仅列出部分字段,特殊的前缀&返回一个指向结构体的指针:
package main
import "fmt"
type Vertex struct {
    X int
    Y int
}
var (
    v1 = Vertex{1, 2}
    v2 = Vertex{
        X: 1,
        Y: 6,
    }
    v3 = Vertex{}
    p  = &Vertex{1, 2}
)
func main() {
    fmt.Println(v1, p, v2, v3)
}
------
{1 2} &{1 2} {1 6} {0 0}
3.3 数组
类型[n]T是独立的类型:
var a [10]int
即数组的长度是其类型的一部分,因此数组不能改变大小。
package main
import "fmt"
func main() {
    var a [2]string
    a[0] = "Hello"
    a[1] = "World"
    fmt.Println(a[0], a[1])
    fmt.Println(a)
    primes := [6]int{2, 3, 5, 7, 11, 13}
    fmt.Println(primes)
}
------
Hello World
[Hello World]
[2 3 5 7 11 13]
3.4 切片
每个数组的大小都是固定的,而切片则为数组元素提供动态大小的灵活视角。
类型[]T表示一个元素类型为T的切片,通过上界和下界来界定:
a[low:high]
这是一个左开右闭区间,两个下标均可以省略:
package main
import "fmt"
func main() {
    primes := [6]int{2, 3, 5, 7, 11, 13}
    fmt.Println(primes, len(primes), cap(primes))
    var s1 = primes[1:4]
    fmt.Println(s1, len(s1), cap(s1))
    var s2 = primes[:]
    fmt.Println(s2, len(s2), cap(s2))
}
------
[2 3 5 7 11 13] 6 6
[3 5 7] 3 5
[2 3 5 7 11 13] 6 6
切片就像数组的引用
- 切片并不存储任何数据,它只是描述了底层数组中的一段
 - 更改切片的元素会修改其底层数组中对应的元素
 - 与它共享底层数组的切片都会观测到这些修改
 
package main
import "fmt"
func main() {
    names := [4]string{
        "John",
        "Paul",
        "George",
        "Ringo",
    }
    fmt.Println("The original array:", names)
    a := names[0:2]
    b := names[1:3]
    fmt.Println("\nSlice a:", a)
    fmt.Println("Slice b:", b)
    b[0] = "XXX"
    fmt.Println("\nNow slice a:", a)
    fmt.Println("Now slice b:", b)
    fmt.Println("\nThe modified array:", names)
}
------
The original array: [John Paul George Ringo]
Slice a: [John Paul]
Slice b: [Paul George]
Now slice a: [John XXX]
Now slice b: [XXX George]
The modified array: [John XXX George Ringo]
切片文法
切片文法类似于没有长度的数组文法:
package main
import "fmt"
func main() {
    q := []int{2, 3, 5, 7, 11, 13}
    fmt.Println(q)
    r := []bool{true, false, true, true, false, true}
    fmt.Println(r)
    s := []struct {
        i int
        b bool
    }{
        {2, true},
        {3, false},
        {5, true},
        {7, true},
        {11, false},
        {13, true},
    }
    fmt.Println(s)
}
------
[2 3 5 7 11 13]
[true false true true false true]
[{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
默认行为
切片下界默认值为0,上界则是该切片的长度。
package main
import (
    "fmt"
    "unsafe"
)
func main() {
    s := []int{2, 3, 5, 7, 11, 13}
    fmt.Println(" s:", s)
    s1 := s[1:4]
    fmt.Println("s1:  ", s1)
    s2 := s1[:2]
    fmt.Println("s2:  ", s2)
    s3 := s2[1:]
    fmt.Println("s3:    ", s3)
    s3[0] = 0
    fmt.Println("\nSizeof int on a 64-bit machine:", unsafe.Sizeof(s[0]))
    fmt.Println("\ns[0] location:", &s[0])
    fmt.Println("s[1] location:", &s[1])
    fmt.Println("s[2] location:", &s[2])
    fmt.Println("s[3] location:", &s[3])
}
------
 s: [2 3 5 7 11 13]
s1:   [3 5 7]
s2:   [3 5]
s3:     [5]
Sizeof int on a 64-bit machine: 8
s[0] location: 0xc00008c030
s[1] location: 0xc00008c038
s[2] location: 0xc00008c040
s[3] location: 0xc00008c048
长度和容量
切片拥有长度和容量:
- 长度就是它所包含的元素个数
 - 容量是从它的第一个元素开始数,到其底层数组元素末尾的个数
 
package main
import "fmt"
func main() {
    s := []int{2, 3, 5, 7, 11, 13}
    printSlice(s)
    // 截取切片使其长度为 0
    s = s[:0]
    printSlice(s)
    // 拓展其长度
    s = s[:4]
    printSlice(s)
    // 舍弃前两个值
    s = s[2:]
    printSlice(s)
}
func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
------
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
nil 切片
切片的零值是nil,长度和容量均为0且没有底层数组:
package main
import "fmt"
func main() {
    var s []int
    fmt.Println(s, len(s), cap(s))
    if s == nil {
        fmt.Println("nil!")
    }
}
------
[] 0 0
nil!
使用 make 创建切片
切片可以用内建函数make来创建,make函数会分配一个元素为零值的数组并返回一个引用它的切片:
package main
import "fmt"
func main() {
    a := make([]int, 5)
    printSlice("a", a)
    b := make([]int, 0, 5)
    printSlice("b", b)
    c := b[:2]
    printSlice("c", c)
    d := c[2:5]
    printSlice("d", d)
}
func printSlice(s string, x []int) {
    fmt.Printf("%s len=%d cap=%d %v\n",
        s, len(x), cap(x), x)
}
------
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]
切片的切片
切片可以包含切片,类似二维切片的概念:
package main
import (
    "fmt"
    "strings"
)
func main() {
    board := [][]string{
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
    }
    board[0][0] = "X"
    board[2][2] = "O"
    board[1][2] = "X"
    board[1][0] = "O"
    board[0][2] = "X"
    for i := 0; i < len(board); i++ {
        fmt.Printf("%s\n", strings.Join(board[i], " "))
    }
}
------
X _ X
O _ X
_ _ O
append 追加切片元素
Go 提供了内建的append()函数,用于向切片追加新元素:
package main
import "fmt"
func main() {
    var s []int
    printSlice(s)
    // 向切片添加一个 0
    s = append(s, 0)
    printSlice(s)
    // 这个切片会按需增长
    s = append(s, 1)
    printSlice(s)
    // 可以一次性添加多个元素
    s = append(s, 2, 3, 4)
    printSlice(s)
}
func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
------
len=0 cap=0 []
len=1 cap=2 [0]
len=2 cap=2 [0 1]
len=5 cap=8 [0 1 2 3 4]
// go1.12.6 windows/amd64:
len=5 cap=6 [0 1 2 3 4]
当切片长度不超过
1024时,每次扩容为原切片长度的两倍(有待考证)
3.5 Range
for循环的range形式可遍历切片slice或映射map:
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
    for i, v := range pow {
        fmt.Printf("2^%d = %d\n", i, v)
    }
}
------
2^0 = 1
2^1 = 2
2^2 = 4
2^3 = 8
2^4 = 16
2^5 = 32
2^6 = 64
2^7 = 128
当使用for range遍历时,每次迭代都会返回两个值:
- 前者
i为当前元素的下标 - 后者
v为该下标对应元素的一份副本 
因此,通过下标
s[i]取值比直接通过v效率更高
可以将下标或值赋予_表示忽略:
for i, _ := range pow
for _, value := range pow
若只需要索引,忽略第二个变量即可:
for i := range pow
3.6 映射
映射map将键映射到值。映射的文法与结构体相似,不过必须有键名:
package main
import "fmt"
type Vertex struct {
    Lat, Long float64
}
var m = map[string]Vertex{
    "Bell Labs": Vertex{
        40.68433, -74.39967,
    },
    "Google": Vertex{
        37.42202, -122.08408,
    },
}
func main() {
    fmt.Println(m)
}
------
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
映射的文法
可以直接省略顶级类型的类型名:
var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}
修改映射
当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。
// 插入或修改元素
m[key] = elem
// 删除元素
delete(m, key)
// 通过双赋值检测某个键是否存在
elem, ok := m[key]
3.7 函数值
函数也是值,可以向其他值一样传递。
函数值可以用作函数的参数或返回值:
package main
import (
    "fmt"
    "math"
)
func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}
func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }
    fmt.Println(hypot(5, 12))
    fmt.Println(compute(hypot))
    fmt.Println(compute(math.Pow))
}
------
13
5
81
3.8 函数的闭包
Go 的函数可以是一个闭包clojure。
闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。
例如,函数adder()返回一个闭包,每个闭包都被绑定在其各自的sum变量上:
package main
import "fmt"
func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}
func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}
------
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90
4. 方法和接口
4.1 方法
Go 没有类,不过可以为结构体类型定义方法。
方法就是一类带特殊的接收者参数的函数,接收者位于func关键字和方法名之间:
package main
import (
    "fmt"
    "math"
)
type Vertex struct {
    X, Y float64
}
func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs())
}
------
5
也可以为非结构体类型声明方法,例如在下面的例子中看到了一个带Abs()方法的数值类型MyFloat:
package main
import (
    "fmt"
    "math"
)
type MyFloat float64
func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}
func main() {
    f := MyFloat(-math.Sqrt2)
    fmt.Println(f.Abs())
}
------
1.4142135623730951
需要注意:
- 接收者(例如
MyFloat)的类型定义和方法声明必须在同一包内 - 不能为内建类型声明方法
 
指针接收者
可以为指针接收者声明方法,这意味着方法内部可以修改接收者指向的值:
package main
import (
    "fmt"
    "math"
)
type Vertex struct {
    X, Y float64
}
func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
    v.X *= f
    v.Y *= f
}
func main() {
    v := Vertex{3, 4}
    v.Scale(10)
    fmt.Println(v.Abs())
}
------
50
方法与指针重定向
带指针参数的函数必须接受一个指针:
var v Vertex
ScaleFunc(v, 5)  // 编译错误!
ScaleFunc(&v, 5) // OK
而以指针为接收者的方法被调用时,接收者既能为值又能为指针:
var v Vertex
v.Scale(5)  // OK
p := &v
p.Scale(10) // OK
这是因为 Go 会对语句做以下翻译:
v.Scale(5)
// 以上语句解释为:
(&v).Scale(5)
同样的,以值为接收者的方法被调用时,接收者既能为值又能为指针:
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
同样会做以下翻译:
p.Abs()
// 以上语句翻译为
(*p).Abs()
选择值或指针作为接收者
通常来说,所有给定类型的方法都应该有值或指针接收者,但不应该二者混用
使用指针接收者的原因有二:
- 方法能够修改其接收者指向的值
 - 可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效
 
例如在下面的示例中,Scale()和Abs()接收者的类型均为*Vertex,即便Abs()并不需要修改其接收者:
package main
import (
    "fmt"
    "math"
)
type Vertex struct {
    X, Y float64
}
func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}
func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
    v := &Vertex{3, 4}
    fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
    v.Scale(5)
    fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}
------
Before scaling: &{X:3 Y:4}, Abs: 5
After scaling: &{X:15 Y:20}, Abs: 25
4.2 接口
接口类型是由一组方法签名定义的集合,接口类型的变量可以保存任何实现了这些方法的值:
package main
import (
    "fmt"
    "math"
)
type Abser interface {
    Abs() float64
}
func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}
    a = f // a MyFloat 实现了 Abser
    fmt.Println(a.Abs())
    a = &v // a *Vertex 实现了 Abser
    fmt.Println(a.Abs())
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}
type Vertex struct {
    X, Y float64
}
func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
------
1.4142135623730951
5
接口的隐式实现
在 Go 中,接口是隐式实现的,即类型通过实现一个接口的所有方法来实现该接口。
既然无需专门显式声明,也就没有implements关键字。
接口值
- 接口也是值,可以像其他值一样传递
 - 接口值可以用作函数的参数或返回值
 - 在内部,接口值可以看做包含值和具体类型的元组:
(value, type) - 接口值保存了一个具体底层类型的具体值
 - 接口值调用方法时会执行其底层类型的同名方法
 
package main
import (
    "fmt"
    "math"
)
type I interface {
    M()
}
type T struct {
    S string
}
func (t *T) M() {
    fmt.Println(t.S)
}
type F float64
func (f F) M() {
    fmt.Println(f)
}
func main() {
    var i I
    i = &T{"Hello"}
    describe(i)
    i.M()
    i = F(math.Pi)
    describe(i)
    i.M()
}
func describe(i I) {
    fmt.Printf("(%v, %T)\n", i, i)
}
------
(&{Hello}, *main.T)
Hello
(3.141592653589793, main.F)
3.141592653589793
底层值为 nil 的接口值
即便接口内的具体值为nil,方法仍然会被nil接收者调用:
package main
import "fmt"
type I interface {
    M()
}
type T struct {
    S string
}
func (t *T) M() {
    if t == nil {
        fmt.Println("<nil>")
        return
    }
    fmt.Println(t.S)
}
func main() {
    var i I
    var t *T
    i = t
    describe(i)
    i.M()
    i = &T{"hello"}
    describe(i)
    i.M()
}
func describe(i I) {
    fmt.Printf("(%v, %T)\n", i, i)
}
------
(<nil>, *main.T)
<nil>
(&{hello}, *main.T)
hello
注意:保存了
nil具体值的接口,其自身并不为nil
nil 接口值
nil接口值既不保存值也不保存具体类型- 为
nil接口调用方法会产生运行时错误 
package main
import "fmt"
type I interface {
    M()
}
func main() {
    var i I
    describe(i)
    i.M()
}
func describe(i I) {
    fmt.Printf("(%v, %T)\n", i, i)
}
------
(<nil>, <nil>)
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xd9864]
goroutine 1 [running]:
main.main()
    /tmp/sandbox062767685/prog.go:12 +0x84
空接口
指定了零个方法的接口值被称为空接口:
interface{}
- 空接口可以用来保存任何类型的值,因为每个类型都至少实现了零个方法
 - 空接口被用来处理未知类型的值
 
package main
import "fmt"
func main() {
    var i interface{}
    describe(i)
    i = 42
    describe(i)
    i = "hello"
    describe(i)
}
func describe(i interface{}) {
    fmt.Printf("(%v, %T)\n", i, i)
}
------
(<nil>, <nil>)
(42, int)
(hello, string)
4.3 类型断言
类型断言提供了访问接口值底层具体值的方式:
t, ok := i.(T)
- 若
i保存了一个T,那么t将会是其底层值,而ok为true - 否则,
ok将为false,而t将为T类型的零值,程序并不会产生panic 
package main
import "fmt"
func main() {
    var i interface{} = "hello"
    s := i.(string)
    fmt.Println(s)
    s, ok := i.(string)
    fmt.Println(s, ok)
    f, ok := i.(float64)
    fmt.Println(f, ok)
    f = i.(float64) // 报错(panic)
    fmt.Println(f)
}
------
hello
hello true
0 false
panic: interface conversion: interface {} is string, not float64
goroutine 1 [running]:
main.main()
    /tmp/sandbox590758285/prog.go:17 +0x220
4.4 类型选择
类型选择是一种按顺序从几个类型断言中选择分支的结构:
package main
import "fmt"
func do(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("Twice %v is %v\n", v, v*2)
    case string:
        fmt.Printf("%q is %v bytes long\n", v, len(v))
    default:
        fmt.Printf("I don't know about type %T!\n", v)
    }
}
func main() {
    do(21)
    do("hello")
    do(true)
}
------
Twice 21 is 42
"hello" is 5 bytes long
I don't know about type bool!
4.5 Stringer
fmt包中定义的Stringer接口是最普遍的接口之一:
type Stringer interface {
    String() string
}
因此只要实现了String()方法,就可以打印结构体的信息:
package main
import "fmt"
type Person struct {
    Name string
    Age  int
}
func (p Person) String() string {
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
func main() {
    a := Person{"Arthur Dent", 42}
    z := Person{"Zaphod Beeblebrox", 9001}
    fmt.Println(a, z)
}
------
Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)
4.6 错误
Go 程序使用error值来表示错误状态。与fmt.Stringer类似,error类型也是一个内建接口:
type error interface {
    Error() string
}
通常函数会返回一个error值,调用它的代码应当判断这个错误是否等于nil来进行错误处理:
package main
import (
    "fmt"
    "time"
)
type MyError struct {
    When time.Time
    What string
}
func (e *MyError) Error() string {
    return fmt.Sprintf("at %v, %s", e.When, e.What)
}
func run() error {
    return &MyError{
        time.Now(),
        "it didn't work",
    }
}
func main() {
    if err := run(); err != nil {
        fmt.Println(err)
    }
}
------
at 2019-08-18 12:53:38.540727 +0800 CST m=+0.002985501, it didn't work
4.7 Reader
io包指定了io.Reader接口,它表示从数据流的末尾进行读取。
io.Reader接口有一个Read方法:
func (T) Read(b []byte) (n int, err error)
该方法用数据填充给定的字节切片,并返回填充的字节数和错误值。在遇到数据流的结尾时,会返回一个io.EOF错误:
package main
import (
    "fmt"
    "io"
    "strings"
)
func main() {
    r := strings.NewReader("Hello, Reader!")
    b := make([]byte, 8)
    for {
        n, err := r.Read(b)
        fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
        fmt.Printf("b[:n] = %q\n", b[:n])
        if err == io.EOF {
            break
        }
    }
}
------
n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""
4.8 图像
image包定义了Image接口:
package image
type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}
这些接口和类型由image/color包定义:
package main
import (
    "fmt"
    "image"
)
func main() {
    m := image.NewRGBA(image.Rect(0, 0, 100, 100))
    fmt.Println(m.Bounds())
    fmt.Println(m.At(0, 0).RGBA())
}
------
(0,0)-(100,100)
0 0 0 0
5. 并发
5.1 协程 goroutine
Go 程goroutine是由 Go 运行时管理的轻量级线程:
go f(x, y, z)
上面的语句会启动一个新的 Go 程并执行:
f(x, y, z)
f, x, y, z的求值发生在当前 Go 程中f的执行发生在新的 Go 程中
package main
import (
    "fmt"
    "time"
)
func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}
func main() {
    go say("world")
    say("hello")
}
------
hello
world
world
hello
world
hello
world
hello
hello
Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步。sync包提供了这种能力,不过也可以利用其它方式实现。
5.2 通道 channel
信道channel是带有类型的管道,可以通过它使用信道操作符<-来发送或者接收值:
ch <- v    // 将 v 发送至信道 ch
v := <-ch  // 从 ch 接收值并赋予 v
根据箭头在信道的方向,左读右写。
和映射与切片一样,信道在使用前必须创建:
ch := make(chan int)
默认情况下是阻塞的,这使得 Go 程序可以在没有显式的锁或竞态变量的情况下进行同步:
package main
import "fmt"
func sum(s []int, c chan int) {
    sum := 0
    for i := range s {
        sum += s[i]
    }
    c <- sum // 将和送入 c
}
func main() {
    s := []int{7, 2, 8, -9, 4, 0}
    c := make(chan int)
    go sum(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    x, y := <-c, <-c // 从 c 中接收
    fmt.Println(x, y, x+y)
}
------
-5 17 12
5.3 带缓冲的通道
信道可以是带缓冲的:
ch := make(chan int, 100)
仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接收方会阻塞。
5.4 range 和 close
发送者可通过close关闭一个信道来表示没有需要发送的值了。
接收者可通过以下语句判断信道是否已被关闭:
v, ok := <-ch // 关闭时 v 为默认零值,ok 为 false
循环for i := range c会不断从信道接收值,直到它被关闭:
package main
import (
    "fmt"
)
func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    close(c)
}
func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
    v, ok := <-c
    fmt.Println(v, ok)
}
------
0
1
1
2
3
5
8
13
21
34
0 false
- 只有发送者才能关闭信道,而接收者不能
 - 向一个已经关闭的信道发送数据会引发
panic - 重复关闭信道会引发
panic - 信道与文件不同,通常情况下无需关闭,只有在必须告诉接收者不再有需要发送的值时才有必要关闭,例如终止一个
range循环 
5.5 select 语句
select语句使一个 Go 程可以等待多个通信操作。
select会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时,会随机选择一个执行:
package main
import "fmt"
func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}
func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}
------
0
1
1
2
3
5
8
13
21
34
quit
当select中的其他分支都没有准备好时,default分支就会执行:
select {
case i := <-c:
    // 使用 i
default:
    // 从 c 中接收会阻塞时执行
}
为了在尝试发送或者接收时不发生阻塞,可使用default分支:
package main
import (
    "fmt"
    "time"
)
func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)
    for {
        select {
        case <-tick:
            fmt.Println("tick.")
        case <-boom:
            fmt.Println("BOOM!")
        default:
            fmt.Println("    .")
            time.Sleep(50 * time.Millisecond)
        }
    }
}
------
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
BOOM!
5.6 sync.Mutex
Go 标准库中提供了sync.Mutex互斥锁类型及其两个方法:Lock()、Unlock():
package main
import (
    "fmt"
    "sync"
    "time"
)
// SafeCounter 的并发使用是安全的
type SafeCounter struct {
    v   map[string]int
    mux sync.Mutex
}
// Inc 增加给定 key 的计数器的值
func (c *SafeCounter) Inc(key string) {
    c.mux.Lock()
    c.v[key]++
    c.mux.Unlock()
}
// Value 返回给定 key 的计数器的当前值
func (c *SafeCounter) Value(key string) int {
    c.mux.Lock()
    defer c.mux.Unlock()
    return c.v[key]
}
func main() {
    c := SafeCounter{
        v: make(map[string]int),
    }
    for i := 0; i < 1000; i++ {
        go c.Inc("somekey")
    }
    time.Sleep(time.Second)
    fmt.Println(c.Value("somekey"))
}
------
1000
- 可以通过在代码前调用
Lock()方法、在代码后调用Unlock()方法来保证一段代码的互斥执行 - 也可以用
defer语句来保证互斥锁一定会被解锁 
6. 常用代码
6.1 标准库
// fmt
fmt.Errorf("%s", "db connect fail")
// io
// math
math.Pi
// sync
// strings
strings.Join(board[i], " ")
fileds := strings.Fileds(s)
// strconv
// net/http
// net/url
// log
// types
// json
// xml
// rand
fmt.Println("My favorite number is", rand.Intn(10))
 %d 整型
 %s 字符串
 %f 浮点数
 %T 类型
 %v 值,例如 {3 4}
%+v 域+值,例如 {X:3 Y:4}
 %q 带引号的字符串, "s"
// time
time.Now()
time.Now().Hour()
time.Sleep(time.Second)
// unsafe
unsafe.Sizeof()
// runtime
runtime.GOOS
runtime.GOARCH
runtime.Version()
fmt.Println(runtime.GOMAXPROCS(0))
// errors
errors.New()
// os.Open()
6.2 内建函数
make([]int, 4, 8)
// 切片 slice
len()
cap()
append()
copy()
// 映射 map
delete(m, key)
// 内建接口
type error interface {
    Error()
}
type Stringer interface {
    String() string
}
// 通道
for i := range ch
close(ch)
7. 练习解答
7.1 循环与函数:牛顿法求平方根
package main
import (
    "fmt"
    "math"
)
func sqrt(x float64) float64 {
    z := float64(1)
    for {
        y := z - (z*z-x)/(2*z)
        if math.Abs(y-z) < 1e-10 {
            return y
        }
        z = y
    }
}
func main() {
    fmt.Println(sqrt(2))
    fmt.Println(math.Sqrt(2))
}
------
1.4142135623730951
1.4142135623730951
7.2 切片:图像灰度值
package main
import (
    "golang.org/x/tour/pic"
)
func Pic(dx, dy int) [][]uint8 {
    ret := make([][]uint8, dy)
    for x := 0; x < dy; x++ {
        ret[x] = make([]uint8, dx)
        for y := 0; y < dx; y++ {
            ret[x][y] = uint8(x ^ y)
            // ret[x][y] = uint8((x + y) / 2)
            // ret[x][y] = uint8(x * y)
            // ret[x][y] = uint8(float64(x) * math.Log(float64(y)))
            // ret[x][y] = uint8(x % (y + 1))
        }
    }
    return ret
}
func main() {
    pic.Show(Pic)
}
7.3 映射:单词统计
package main
import (
    "strings"
    "golang.org/x/tour/wc"
)
func WordCount(s string) map[string]int {
    count := make(map[string]int)
    for _, word := range strings.Fields(s) {
        count[word]++
    }
    return count
}
func main() {
    wc.Test(WordCount)
}
7.4 闭包:斐波那契数列
package main
import "fmt"
// 返回一个“返回int的函数”
func fibonacci() func() int {
    one := 0
    two := 1
    return func() int {
        three := one + two
        one = two
        two = three
        return three
    }
}
func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}
------
1
2
3
5
8
13
21
34
55
89
7.5 Stringer
package main
import "fmt"
type IPAddr [4]byte
func (ip IPAddr) String() string {
    return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3])
}
func main() {
    hosts := map[string]IPAddr{
        "loopback":  {127, 0, 0, 1},
        "googleDNS": {8, 8, 8, 8},
    }
    for name, ip := range hosts {
        fmt.Printf("%v: %v\n", name, ip)
    }
}
------
loopback: 127.0.0.1
googleDNS: 8.8.8.8
7.6 错误
package main
import (
    "fmt"
    "math"
)
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}
func sqrt(x float64) (float64, error) {
    if x < 0 {
        return 0, ErrNegativeSqrt(x)
    }
    z := float64(1)
    for {
        y := z - (z*z-x)/(2*z)
        if math.Abs(y-z) < 1e-10 {
            return y, nil
        }
        z = y
    }
}
func main() {
    fmt.Println(sqrt(2))
    fmt.Println(sqrt(-2))
}
------
1.4142135623730951 <nil>
0 cannot Sqrt negative number: -2
7.7 Reader
package main
import (
    "strings"
    "golang.org/x/tour/reader"
)
type MyReader struct{}
func (MyReader) Read(b []byte) (int, error) {
    r := strings.NewReader("A")
    n, err := r.Read(b)
    return n, err
}
func main() {
    reader.Validate(MyReader{})
}
7.8 rot13Reader
package main
import (
    "io"
    "os"
    "strings"
)
type rot13Reader struct {
    r io.Reader
}
func (self rot13Reader) Read(buf []byte) (int, error) {
    length, err := self.r.Read(buf)
    if err != nil {
        return length, err
    }
    for i := 0; i < length; i++ {
        v := buf[i]
        switch {
        case 'a' <= v && v <= 'm':
            fallthrough
        case 'A' <= v && v <= 'M':
            buf[i] = v + 13
        case 'n' <= v && v <= 'z':
            fallthrough
        case 'N' <= v && v <= 'Z':
            buf[i] = v - 13
        }
    }
    return length, nil
}
func main() {
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    io.Copy(os.Stdout, &r)
}
------
You cracked the code!
7.9 图像
package main
import (
    "image"
    "image/color"
    "golang.org/x/tour/pic"
)
type Image struct {
    w int
    h int
}
func (self Image) ColorModel() color.Model {
    return color.RGBAModel
}
func (self Image) Bounds() image.Rectangle {
    return image.Rect(0, 0, self.w, self.h)
}
func (self Image) At(x, y int) color.Color {
    r := (uint8)((float64)(x) / (float64)(self.w) * 255.0)
    g := (uint8)((float64)(y) / (float64)(self.h) * 255.0)
    b := (uint8)((float64)(x*y) / (float64)(self.w*self.h) * 255.0)
    return color.RGBA{r, g, b, 255}
}
func main() {
    m := Image{255, 255}
    pic.ShowImage(m)
}
7.10 等价二叉查找树
package main
import (
    "fmt"
    "golang.org/x/tour/tree"
)
// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func Walk(t *tree.Tree, ch chan int) {
    if t == nil {
        return
    }
    Walk(t.Left, ch)
    ch <- t.Value
    Walk(t.Right, ch)
}
// Same 检测树 t1 和 t2 是否含有相同的值。
func Same(t1, t2 *tree.Tree) bool {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go Walk(t1, ch1)
    go Walk(t2, ch2)
    for i := 0; i < 10; i++ {
        x, y := <-ch1, <-ch2
        fmt.Println(x, y)
        if x != y {
            return false
        }
    }
    return true
}
func main() {
    fmt.Println(Same(tree.New(1), tree.New(1)))
}
------
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
true
7.11 Web 爬虫
package main
import (
    "fmt"
    "sync"
)
type Fetcher interface {
    // Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
    Fetch(url string) (body string, urls []string, err error)
}
// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher, crawled Crawled, out chan string, end chan bool) {
    // TODO: 并行的抓取 URL。
    // TODO: 不重复抓取页面。
    // 下面并没有实现上面两种情况:
    if depth <= 0 {
        end <- true
        return
    }
    crawled.mux.Lock()
    if _, ok := crawled.crawled[url]; ok {
        crawled.mux.Unlock()
        end <- true
        return
    }
    crawled.crawled[url] = 1
    crawled.mux.Unlock()
    _, urls, err := fetcher.Fetch(url)
    if err != nil {
        fmt.Println(err)
        end <- true
        return
    }
    out <- url
    //fmt.Println("found: ", url, body)
    for _, u := range urls {
        go Crawl(u, depth-1, fetcher, crawled, out, end)
    }
    for i := 0; i < len(urls); i++ {
        <-end
    }
    end <- true
    return
}
type Crawled struct {
    crawled map[string]int
    mux     sync.Mutex
}
func main() {
    crawled := Crawled{make(map[string]int), sync.Mutex{}}
    out := make(chan string)
    end := make(chan bool)
    go Crawl("http://golang.org/", 4, fetcher, crawled, out, end)
    for {
        select {
        case url := <-out:
            fmt.Println("found: ", url)
        case <-end:
            return
        }
    }
}
// fakeFetcher 是返回若干结果的 Fetcher。
type fakeFetcher map[string]*fakeResult
type fakeResult struct {
    body string
    urls []string
}
func (f fakeFetcher) Fetch(url string) (string, []string, error) {
    if res, ok := f[url]; ok {
        return res.body, res.urls, nil
    }
    return "", nil, fmt.Errorf("not found: %s", url)
}
// fetcher 是填充后的 fakeFetcher。
var fetcher = fakeFetcher{
    "http://golang.org/": &fakeResult{
        "The Go Programming Language",
        []string{
            "http://golang.org/pkg/",
            "http://golang.org/cmd/",
        },
    },
    "http://golang.org/pkg/": &fakeResult{
        "Packages",
        []string{
            "http://golang.org/",
            "http://golang.org/cmd/",
            "http://golang.org/pkg/fmt/",
            "http://golang.org/pkg/os/",
        },
    },
    "http://golang.org/pkg/fmt/": &fakeResult{
        "Package fmt",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
    "http://golang.org/pkg/os/": &fakeResult{
        "Package os",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
}
------
found:  http://golang.org/
found:  http://golang.org/pkg/
found:  http://golang.org/pkg/os/
found:  http://golang.org/pkg/fmt/
not found: http://golang.org/cmd/
8. 参考文章
官方文档
- Go Tour | Golang.org
 - Go Wiki | Github
 - 实效 Go 编程 | Golang.org
 - Effective Go | Golang.org
 - Package builtin - 内建函数 | Golang.org
 - Packages - 标准库 | Golang.org
 - GoDoc | Search for Go Packages
 
Go Blog
Go Tour 题解
Go 标准库
推荐一个知乎专栏作者:谢伟,知乎专栏『Gopher』- Go 上手指南
- Go 内置库第一季:strings - 谢伟 | 知乎
 - Go 内置库第一季:strconv - 谢伟 | 知乎
 - Go 内置库第一季:reflect - 谢伟 | 知乎
 - Go 内置库第一季:json - 谢伟 | 知乎
 - Go 内置库第一季:error - 谢伟 | 知乎
 - Go 内置库第一季:time - 谢伟 | 知乎
 - Go 内置库第一季:net/url - 谢伟 | 知乎
 - 请教:FieldsFunc 函数的用法 | Golang 中国
 - 【Go语言】基本类型排序和 slice 排序 | iTimeTraveler
 
其他不错的文章:
- Go Web 教程 - 谢伟 | 知乎
 - Go GraphQL 教程 - 谢伟 | 知乎
 - Go 与 Error 的前世今生 - 谢伟 | 知乎
 - 自己构建节假日 API - 谢伟 | 知乎
 - 打造一款 emoji 表情库 - 谢伟 | 知乎
 
51CTO 上的一个系列教程
- Go 语言开发学习教程 - 天山老妖S | 51CTO
 - Go 语言常用标准库一 - 天山老妖S | 51CTO
 - Go 语言常用标准库二 - 天山老妖S | 51CTO
 - Go 语言常用标准库三 - 天山老妖S | 51CTO
 - Go 语言常用标准库四 - 天山老妖S | 51CTO
 - Go 语言常用标准库五 - 天山老妖S | 51CTO
 - Go 语言常用标准库六 - 天山老妖S | 51CTO
 - Go 语言 MySQL 数据库操作 - 天山老妖S | 51CTO
 - Go 语言 database/sql 接口 - 天山老妖S | 51CTO
 
设计模式
演讲 PPT
- Go: a simple programming environment - Andrew Gerrand | Google
 - Go Concurrency Patterns - Rob Pike | Google
 
Web 编程
- build-web-application-with-golang - astaxie | Github
 - Writing Web Applications | golang.org
 - Beego Framework
 - Build a Single-Page App with Go and Vue | okta
 - Go Vue Template - tdewolff | Github