Go Quick Memo

基础

  • 导入包

    import "fmt"
    import "math"
    //圆括号“打包”导入
    import (
        "fmt"
        "math/rand"
    )
    
  • 函数

    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,返回各个返回变量的当前值。

  • 变量

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

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

  • 基本类型

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

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

  • 类型推导

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

  • 常量

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

控制结构

  • 循环

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

    for i:=0; i < 10; i++ {
        sum += i
    }
    // 初始化语句和后置语句可选
    sum := 1
    for ; sum < 1000; {
        sum += sum
    }
    

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

    sum := 1
    for sum < 1000 {
        sum += sum
    }
    // 死循环
    for {
    }
    
  • 条件

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

    switch fruit := "apple"; fruit {
        case "banana":
            do-sth
        default:
            do-sth
    }
    

    没有条件的 switch 等同于 switch true

  • defer

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

    func main() {
        defer fmt.Println("world")
        fmt.Println("hello")
    }
    

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

复杂类型

  • 指针

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

    var p *int
    i := 42
    p = &i
    fmt.Println(*p)
    *p = 21
    

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

  • 结构体

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

    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: 仅列出部分字段(顺序无关)。& 前缀返回指向结构体的指针。

    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 的值的数组。

    var a [10]int
    
  • slice

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

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

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

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

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

    append(a, 0, 1, 2)
    

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

  • range

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

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

  • map

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

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

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

    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

    m[key] = elem
    elem = m[key]
    delete(m, key)
    elem, ok = m[key]  // true or false
    

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

  • 函数值

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

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

    // 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 关键字和方法名之间的参数中。

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

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

  • 接口

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

    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

    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 类型是一个内建接口:

    type error interface {
        Error() string
    }
    

并发

  • goroutine

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

    func main() {
        go hello("hey")
        hello("hey")
    }
    

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

  • channel

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

    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 语句来保证互斥锁一定会被解锁。