摘自 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 分支

switchcase语句从上到下顺次执行,直到匹配成功时停止:

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)
    }
}

没有条件的switchswitch 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

更多内容参见 Defer, Panic, and Recover | The Go Blog

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 切片:用法和本质 | Go Blog

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()

选择值或指针作为接收者

通常来说,所有给定类型的方法都应该有值或指针接收者,但不应该二者混用

使用指针接收者的原因有二:

  1. 方法能够修改其接收者指向的值
  2. 可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效

例如在下面的示例中,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将会是其底层值,而oktrue
  • 否则,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. 练习解答

参见 Golang 官方指导练习 - 掠雪墨影 | CSDN

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 Blog

Go Tour 题解

Go 标准库

推荐一个知乎专栏作者:谢伟,知乎专栏『Gopher』- Go 上手指南

其他不错的文章:

51CTO 上的一个系列教程

演讲 PPT

Web 编程

开源图书

游戏