Go Quick Memo

基础

  • 导入包

    1
    2
    3
    4
    5
    6
    7
    import "fmt"
    import "math"
    //圆括号“打包”导入
    import (
    "fmt"
    "math/rand"
    )
  • 函数

    1
    2
    3
    func add(x int, y int) int { return x + y }
    func add(x, y int) int {}
    func swap(x, y string) (string, string) { return y, x } //多值返回

    裸返回,仅有 return,返回各个返回变量的当前值。

  • 变量

    1
    2
    3
    4
    5
    6
    7
    var a, b bool
    var i, j int = 1, 2
    k := 3 //简洁赋值,不能用于函数外
    var (
    m int = 6
    n int = 7
    )

    变量没有明确初始化时会赋值为零值, 0false""

  • 基本类型

    1
    2
    3
    bool  string  [u]int[8-64]  float[32,64]  complex[64,128]
    byte //uint8 的别名
    rune //int32 的别名,代表一个 Unicode 码
  • 类型转换

    T(v) 将值 v 转换为类型 T 的。需要显式转换。

  • 类型推导

    定义了变量却不显式指定类型时,类型由右侧值推导得出。若右侧值有类型,则与右侧值类型相同。若式未指明类型的数字,还与常量的精度有关。

  • 常量

    常量使用关键字 const,不能使用 := 语法定义。

控制结构

  • 循环

    Go 只有一种循环结构, for 循环。

    1
    2
    3
    4
    5
    6
    7
    8
    for i:=0; i < 10; i++ {
    sum += i
    }
    // 初始化语句和后置语句可选
    sum := 1
    for ; sum < 1000; {
    sum += sum
    }

    由于分号可省略,替代 while 的作用:

    1
    2
    3
    4
    5
    6
    7
    sum := 1
    for sum < 1000 {
    sum += sum
    }
    // 死循环
    for {
    }
  • 条件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if x < 0 {
    return y
    }
    if v := math.Pow(x, n); v < lim {
    return v
    } else {
    blahblah
    }
    // 便捷语句定义变量有效范围
  • Switch

    1
    2
    3
    4
    5
    6
    switch fruit := "apple"; fruit {
    case "banana":
    do-sth
    default:
    do-sth
    }

    没有条件的 switch 等同于 switch true

  • defer

    延迟函数的执行直到上层函数返回。参数会立刻生成,但是上层函数返回前不会被调用。

    1
    2
    3
    4
    5
    func main() {
    defer fmt.Println("world")
    fmt.Println("hello")
    }

    延迟的函数调用压入栈中,后进先出调用。

复杂类型

  • 指针

    指针保存了变量的内存地址。类型 *T 是指向类型 T 的值的指针。零值是 nil

    1
    2
    3
    4
    5
    var p *int
    i := 42
    p = &i
    fmt.Println(*p)
    *p = 21

    间接引用。Go 没有指针运算。

  • 结构体

    一个结构体( struct)就是一个字段的集合。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    type Vertex struct {
    X int
    Y int
    }
    ...
    fmt.Println(Vertex{1, 2})

    // 结构体访问
    v := Vertex{1, 2}
    v.X = 2
    fmt.Println(v.Y)

    // 通过结构体指针访问
    p := &v
    p.X = 3

    结构体文法:通过结构体字段的值作为列表来新分配一个结构体。 Name: 仅列出部分字段(顺序无关)。 & 前缀返回指向结构体的指针。

    1
    2
    3
    4
    5
    6
    var (
    v1 = Vertex{1, 2} // {1, 2}
    v2 = Vertex{X: 1} // {1, 0}
    v3 = Vertex{} // {0, 0}
    p = &Vertex{1, 2} // &{1, 2}
    )
  • 数组

    [n]T n 个类型 T 的值的数组。

    1
    2
    var a [10]int

  • slice

    []T 是一个元素类型为 T 的 slice。 len(s) 返回 s 的长度。零值是 nil

    1
    s := []int{2, 3, 4}

    slice 可以包含任意的类型,包括 slice。可重新切片。

    1
    2
    3
    4
    // 使用 make 构造 slice,len(a)=5
    a := make([]int, 5)
    // 参数指定容量,长度 0,容量 5
    b := make([]int, 0, 5)

    append 向末尾添加元素,若超出原大小,分配一个更大的数组:

    1
    2
    append(a, 0, 1, 2)

    切片操作并不会复制底层的数组,整个数组将被保存在内存中,直到它不再被引用。

  • range

    1
    2
    3
    var pow = []int{1, 3, 5, 7}
    for i, v := range pow {
    blahblah // i 下标序号 v 对应元素的一个拷贝

    可通过赋值给 _ 来忽略值。

  • map

    map 映射键值。使用前需要 make 创建;值为 nil 的 map 是空的,并且不能对其赋值。

    1
    2
    3
    var m map[string]int
    m = make(map[string]int)
    m["apple"] = 2

    map 文法与结构体文法相似,但必须有键名。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    type Info struct {
    num, price int
    }

    var n = map[string]Info{
    "banana": Info{1, 2},
    "pear": Info{3, 4},
    }
    // 顶级类型只是一个类型名则可以省略
    var q = map[string]Info{
    "banana": {1, 2},
    "pear": {3, 4},
    }

    修改 map

    1
    2
    3
    4
    m[key] = elem
    elem = m[key]
    delete(m, key)
    elem, ok = m[key] // true or false

    读取不存在的键,结果是元素类型的零值。

  • 函数值

    函数也是值,可以传递,如作为函数参数或返回值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    func compute(fn func(int, int) int) int {
    return fn(1, 2)
    }

    func main() {
    add := func(x, y int) int {
    return x + y
    }
    fmt.Println(add(5, 12))
    fmt.Println(compute(add))
    }
  • 函数的闭包

    Go 函数可以是一个闭包。闭包是一个函数值,它引用了函数体之外的变量。函数可以对这个引用的变量进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // fibonacci 函数会返回一个返回 int 的函数。
    func fibonacci() func() int {
    first := 0
    second :=1
    fib := 0
    return func() int {
    fib = first + second
    first, second = second, first
    first = fib
    return fib
    }
    }

    func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
    fmt.Println(f())
    }
    }

方法和接口

  • 方法

    Go 没有类,但仍可以在结构体类型上定义方法。 方法接收者func 关键字和方法名之间的参数中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type Fruit struct {
    num, price int
    }

    func (m *Fruit) Total() int {
    return m.num * m.price
    }
    ...
    m := &Fruit{2, 5}
    fmt.Println(m.Total())

    可以对包中的 任意 类型定义方法,不仅仅是针对结构体,但不能对来自其它包的类型或基础类型进行定义。方法可以与命名类型或命令类型的指针关联,不使用指针的话,方法调用的是拷贝值。

  • 接口

    接口类型是一组方法定义的集合。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    type Abser interface {
    Abs() float64
    }

    func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    a = f
    fmt.Println(a.Abs())
    }

    type MyFloat float64

    func (f MyFloat) Abs() float64 {
    if f < 0 {
    return float64(-f)
    }
    return float64(f)
    }

    fmt 中的 Stringer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    type IPAddr [4]byte

    func (addr IPAddr) String() string {
    return fmt.Sprintf("\"%v.%v.%v.%v\"", addr[0], addr[1], addr[2], addr[3])
    }

    func main() {
    addrs := map[string]IPAddr{
    "loopback": {127, 0, 0, 1},
    "googleDNS": {8, 8, 8, 8},
    }
    for n, a := range addrs {
    fmt.Printf("%v: %v\n", n, a)
    }
    }
  • 错误

    error 类型是一个内建接口:

    1
    2
    3
    type error interface {
    Error() string
    }

并发

  • goroutine

    goroutine 是由 Go 运行时环境管理的轻量级线程。

    1
    2
    3
    4
    func main() {
    go hello("hey")
    hello("hey")
    }

    函数和参数均为当前 goroutine 中定义的,但在新的 goroutine 中运行函数。

  • channel

    channel 是有类型的管道,可以用 <- 操作符对其发送或接收值。箭头就是数据流的方向。channel 使用前必须创建。

    1
    2
    3
    ch := make(chan int)
    ch <- v //将 v 传入 channel ch
    v := <- ch // 从 ch 接收并赋值给 v
  • 缓冲 channel

    通过为 make 提供第二个参数作为缓冲长度来初始化 channel,向其发送数据时,只有缓冲区满的时候才会阻塞。缓冲区为空的时候接收操作会阻塞。

    close(ch) 可以关闭一个 channel 来表示不会再有值被发送了。通过 v, ok := <- ch 的测试,channel 被关闭的话 ok 会被设置为 false

    for i := range ch 会不断从 channel 接收值,直到它被关闭。

    channel 与文件不同,通常情况下无需关闭它们。只有在需要告诉接收者没有更多数据的时候才有必要关闭,比如中断一个 range

  • select

    select 语句使得一个 goroutine 在多个通讯操作上等待。阻塞到条件分支中的某个可以继续执行,有多个可以执行的时候,随机选择一个。其它条件分支都没有准备好的时候, default 分支会被执行。

  • sync.Mutex

    互斥锁, sync.Mutex 类型,方法: Lock Unlock。可以用 defer 语句来保证互斥锁一定会被解锁。