UP | HOME

go-learn-note

Table of Contents

第一章 类型

变量

  • Go是静态类型语言,不能在运行期改变变量的类型
  • 在函数外,使用var来定义变量,初始值为0;如果提供初始化值,可省略变量类型,由编译 器自动推导
    package main
    
    import "fmt"
    
    var globalX int
    var globalY = "abc"
    
    func main() {
        fmt.Println(globalX)
        fmt.Println(globalY)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 0                                              //
    // abc                                            //
    ////////////////////////////////////////////////////
    
  • 在函数内部,还可以使用`:=`初始化,需要注意是新创建的local变量,还是更改了global 变量!
    package main
    
    import "fmt"
    
    var globalX int
    
    func main() {
        localX := 123
        fmt.Println(globalX)
        fmt.Println(localX)
        // Be careful! `:=` can also change the global value
        globalX := 456
        fmt.Println(globalX)
    
        // Error !You can not REDEFINE one variable
        // localX := 789
        // But of cause, you can change it value
        localX = 987
        fmt.Println(localX)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 0                                              //
    // 123                                            //
    // 456                                            //
    // 987                                            //
    ////////////////////////////////////////////////////
    
  • 定义多个变量的方式有如下几种
    package main
    
    import "fmt"
    
    var x, y, z int
    var s, n = "abc", 123
    
    var (
        a int
        b float32
    )
    
    func main() {
        n, s := 0x1234, "Hello, World!"
        fmt.Println(x, s, n)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 0 Hello, World! 4660                           //
    ////////////////////////////////////////////////////
    
  • 多变量赋值是先计算相关值,然后从左到右依次赋值
    package main
    
    import "fmt"
    
    func main() {
        data, i := [3]int{0, 1, 2}, 0
        fmt.Println(i)
        fmt.Println(data)
    
        // When assigning multiple values in one line
        // You should:
        // [1] make sure all the value: (i => 0) and (data[i] => data[0])
        // [2] assign value base on [1]: i = 2, data[0] = 100
        i, data[i] = 2, 100
        fmt.Println(i)
        fmt.Println(data)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 0                                              //
    // [0 1 2]                                        //
    // 2                                              //
    // [100 1 2]                                      //
    ////////////////////////////////////////////////////
    
  • "_"是特殊变量,用户忽略占位符
    package main
    
    import "fmt"
    
    func test() (int, string) {
        return 1, "abc"
    }
    
    func main() {
        _, s := test()
        fmt.Println(s)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // o                                              //
    // abc                                            //
    ////////////////////////////////////////////////////
    
  • "_"的占位符作用还可以体现在规避'编译器认为没使用的局部变量是error'
    var s string  // global variable unused is OK
    
    func main() {
        // Error ! => i declared and not used
        i := 0
        // use following to block previous error
        // _ = i
    }
    
  • 前面介绍过,一个变量是无法重定义(redefine)的,但是两个变量却可以重定义!这个是 go特别的地方:
    • 可以在同一个block里面重定义已定义的变量(但是要两个变量一块的情况):这个也是 可以理解的,这是由go的特性决定的.比如下面的例子,我们s已经定义了,我们想再定义 一个y,而且两个定义写到一句里面(其实函数返回两个值就是这种情况).你不能苛求 我定义y的时候,能把s的值也写对啊,所以s就可以redefine了!
      s := "abc"
      // same level, can redefine if define two variable at once
      s, y := "hello", 20
      
    • 不同的block里面的就不存在所谓的"重定义"了,因为即便是变量名相同,也其实是两个 不同的变量了
  • 重定义例子如下
    package main
    
    import "fmt"
    
    func main() {
        s := "abc"
        fmt.Println(&s)
    
        s, y := "hello", 20
        fmt.Println(&s, y)
    
        {
            s, z := 1000, 30
            fmt.Println(&s, z)
        }
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 0x82023e080                                    //
    // 0x82023e080 20                                 //
    // 0x82023e0b0 30                                 //
    ////////////////////////////////////////////////////
    

常量

  • 常亮必须是编译期可以确定的数字,字符串,布尔值
    package main
    
    import "fmt"
    
    const x, y int = 1, 2
    const s = "Hello, World!"
    
    const (
        a, b      = 10, 100
        c    bool = false
    )
    
    func main() {
        // one day const, always const
        const x = "xxx"
    
        // only x is used
        // unused const variable will not incur Error
        fmt.Println(x)
    }
    
  • 常量还可以是len, cap, unsafe.Sizeof等编译器可确定结果的函数返回值
    package main
    
    import (
        "fmt"
        "unsafe"
    )
    
    func main() {
        const (
            a = "abc"
            b = len(a)
            c = unsafe.Sizeof(b)
        )
        fmt.Println(a, b, c)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // abc 3 8                                        //
    ////////////////////////////////////////////////////
    
  • 常量和iota是好朋友,iota能够提供"自增枚举值":
    • 提供从0开始的自增枚举值是最简单的
      package main
      
      import "fmt"
      
      func main() {
          const (
              Sunday = iota
              Monday
              Tuesday
              Wednesday
              Thursday
              Friday
              Saturday
          )
      
          fmt.Println(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // 0 1 2 3 4 5 6                                  //
      ////////////////////////////////////////////////////
      
    • 当前行的规则和"前面一行"的规则相同. 所以MB也是 1 << (10 * iota) 只不过iota是2了
      package main
      
      import "fmt"
      
      func main() {
          const (
              _        = iota             // itoa = 0
              KB int64 = 1 << (10 * iota) // itoa = 1
              MB                          // Same with KB, but itoa = 2 here
              GB
              TB
          )
          fmt.Println(KB, MB, GB, TB)
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // 1024 1048576 1073741824 1099511627776          //
      ////////////////////////////////////////////////////
      
    • 还可以在一组赋值中,多次使用iota
      package main
      
      import "fmt"
      
      func main() {
          const (
              A, B = iota, iota << 10
              C, D
          )
      
          fmt.Println(A, B, C, D)
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // 0 0 1 1024                                     //
      ////////////////////////////////////////////////////
      
    • 如果iota的自增被打断,需要"显示"恢复,所谓显示,就是再输入一次iota
      package main
      
      import "fmt"
      
      func main() {
          const (
              A = iota
              B
              C = "c"
              D        // same with previous
              E = iota // return to interrupted increasing number
              F
          )
          fmt.Println(A, B, C, D, E, F)
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // 0 1 c c 4 5                                    //
      ////////////////////////////////////////////////////
      
    • 如果想让函数的参数"只有枚举类型和常量"可以访问,而普通变量无法轻易的作为函 数参数,那么可以使用"枚举+自定义类型"的方式
      package main
      
      import "fmt"
      
      type Color int
      
      const (
          Black Color = iota
          Red
          Blue
      )
      
      func test(c Color) {
          fmt.Println("Color number is ", c)
      }
      
      func main() {
          c := Black
          test(c)
      
          // Const is auto convert
          test(1)
      
          x := 1
          _ = x
          // Error! can not use x (type int) as type Color in function argument
          // test(x)
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // Color number is  0                             //
      // Color number is  1                             //
      ////////////////////////////////////////////////////
      

基本类型

  • go的类型更加明确,支持Unicode,支持常用数据结构,简单的几个类型如下
    Type Length Default Range
    bool 1 false  
    byte 1 0 unit8
    rune 4 0 Unicode Code Point, int32
    int,uint 4 or 8 0 32bit or 64bit
    float32 4 0.0  
    float64 8 0.0  

引用类型

  • 引用类型(reference type)包括slice, map和channel.它们有复杂的内部结构使用的 时候需要:
    • 申请内存
    • 初始化相关属性
  • 内置函数new计算类型大小,为期分配零值内存,返回指针.而make是分配内存,初始化成 员列表,返回对象而非指针
    package main
    
    import "fmt"
    
    func main() {
        a := []int{0, 0, 0}
        a[1] = 10
    
        fmt.Println(a)
    
        // make return object
        b := make([]int, 3)
        b[1] = 10
    
        fmt.Println(b)
    
        // new return the pointer
        c := new([]int)
        fmt.Println(*c)
    }
    

类型转换

  • 不支持隐式类型转换,即便是从窄转向宽也不行
    var b byte = 100
    // var n int = b // Error: implicit convertation is not supported
    var n int = int(b)              // ok for explicit convertation
    
  • 同样不能将其他类型当成bool值使用
    a := 100
    // Error: non-bool a (type int) used as if condition
    if a {
        //
    }
    

字符串

  • 字符串是immutable类型,内部指针指向UTF-8字节数组:
    • 默认值是空字符串""
    • 用索引号访问某个字节,如s[i]
    • 不能用序列号获取字节元素指针,&s[i]非法
    • 不可变类型,无法修改字节数组
    • 字节数组尾部不包含NULL
  • 使用索引号访问字符(byte)
    package main
    
    import "fmt"
    
    func main() {
        s := "abc"
        fmt.Println(s[0] == '\x61', s[1] == 'b', s[2] == 0x63)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // true true true                                 //
    ////////////////////////////////////////////////////
    
  • 使用"`"定义不做转义处理的原始字符串,支持跨行
    package main
    
    import "fmt"
    
    func main() {
        s := `a
    b\r\n\x00
    c`
    
        fmt.Println(s)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // a                                              //
    // b\r\n\x00                                      //
    // c                                              //
    ////////////////////////////////////////////////////
    
  • 连接字符串时, "+"必须在上一行的末尾
    package main
    
    import "fmt"
    
    func main() {
        // '+' should be at the tail of the line
        s := "Hello, " +
            "World!"
        fmt.Println(s)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Hello, World!                                  //
    ////////////////////////////////////////////////////
    
  • 支持用两个索引号返回子串,子串依然指向原字节数组
    package main
    
    import "fmt"
    
    func main() {
        s := "Hello, World!"
    
        fmt.Println(s[:5])
        fmt.Println(s[7:])
        fmt.Println(s[1:5])
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Hello                                          //
    // World!                                         //
    // ello                                           //
    ////////////////////////////////////////////////////
    
  • 单引号字符常量表示Unicode Code Point
    package main
    
    import "fmt"
    
    func main() {
        fmt.Printf("%T\n", 'a')
    
        var c1, c2 rune = '\u6211', '们'
        fmt.Println(c1 == '我', string(c2) == "\xe4\xbb\xac")
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // int32                                          //
    // true true                                      //
    ////////////////////////////////////////////////////
    
  • 要修改字符串,可以先将其转换成[]rune或者[]byte,完成后再转换成为string,无论 哪种转换,都会重新分配内存,并复制字节数组
    package main
    
    import "fmt"
    
    func main() {
        s := "abcd"
        bs := []byte(s)
    
        bs[1] = 'B'
        fmt.Println(string(bs))
    
        u := "电脑"
        us := []rune(u)
        us[1] = '话'
        fmt.Println(string(us))
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // aBcd                                           //
    // 电话                                           //
    ////////////////////////////////////////////////////
    
  • for 循环遍历也会使用byte和rune两种方式
    package main
    
    import "fmt"
    
    func main() {
        s := "abc汉字"
    
        for i := 0; i < len(s); i++ {
            fmt.Printf("%c, ", s[i])
        }
    
        fmt.Println()
    
        for _, r := range s {
            fmt.Printf("%c,", r)
        }
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // a, b, c, æ, ±, ‰, å, ­, —,               //
    // a,b,c,汉,字,                                   //
    ////////////////////////////////////////////////////
    

指针

  • 支持指针类型*T, 指针的指针**T, 以及包含包名前缀的*<package>.T
  • go的指针的特点有如下:
    • 默认值nil,没有NULL常量
    • 操作符"&"取变量地址, "*"透过指针访问目标对象
    • 不支持指针运算,不支持"->"运算符,直接使用"."访问目标成员
  • 例子如下
    package main
    
    import "fmt"
    
    func main() {
        type data struct{ a int }
    
        var d = data{1234}
        var p *data
    
        p = &d
    
        fmt.Printf("%p, %v\n", p, p.a)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 0xc082008310, 1234                             //
    ////////////////////////////////////////////////////
    
  • 不能对指针做加减法等运算
    x := 1234
    p := &x
    p++
    
  • 可以在unsafe.Pointer和任意类型指针间进行转换
    package main
    
    import (
        "fmt"
        "unsafe"
    )
    
    func main() {
        x := 0x12345678
    
        p := unsafe.Pointer(&x)
        n := (*[4]byte)(p)
    
        for i := 0; i < len(n); i++ {
            fmt.Printf("%X ", n[i])
        }
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 78 56 34 12                                    //
    ////////////////////////////////////////////////////
    
  • 返回局部变量指针是安全的,编译器会根据需要将其分配在GC heap上
    func test() *int {
        x := 100
        return &x
    }
    
  • 将Point而转换成uintptr,可变相实现指针运算
    package main
    
    import (
        "fmt"
        "unsafe"
    )
    
    func main() {
        d := struct {
            s string
            x int
        }{"abc", 100}
    
        p := uintptr(unsafe.Pointer(&d))
        p += unsafe.Offsetof(d.x)
        p2 := unsafe.Pointer(p)
        px := (*int)(p2)
        *px = 200
    
        fmt.Printf("%#v\n", d)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // struct { s string; x int }{s:"abc", x:200}     //
    ////////////////////////////////////////////////////
    

自定义类型

  • 类型其实可以分成两大类:
    • 命名类型,包括bool, int, string
    • 未命名类型,包括array, slice, map
  • 具有相同声明的未命名类型被视为同一类型,比如:
    • 具有相同基类型的指针
    • 具有相同元素类型和长度的array
    • 具有相同元素类型的slice
    • 具有相同键值类型的map
    • 具有相同元素类型和传输方向的channel
    • 具有相同字段序列(字段名,类型,标签,顺序)的匿名struct
    • 签名相同(参数和返回值,不包括参数名称)的function
    • 方法集相同(方法名,方法签名相同,和次序无关)的interface
  • a和b就不被认为是相同类型(不可以相互赋值)
    package main
    
    func main() {
    
        var a struct {
            x int `a`
        }
        var b struct {
            x int `ab`
        }
    
        _ = a
        _ = b
        // Error! cannot use a (type struct { x int "a" })
        //as type struct { x int "ab" } in assignment
        // b = a
    }
    
  • 可用type在全局或函数内定义新类型
    package main
    
    import "fmt"
    
    func main() {
        type bigint int64
    
        var x bigint = 100
    
        fmt.Println(x)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 100                                            //
    ////////////////////////////////////////////////////
    
  • 新类型不是原类型的别名,除拥有相同的数据类型结构外,它们之间没有任何关系,不 会持有原类型任何信息。除非目标类型是未命名类型,否则必须显示转换
    package main
    
    import "fmt"
    
    func main() {
        x := 1234
        type bigint int64
        var b bigint = bigint(x)
        var b2 int64 = int64(b)
    
        type myslice []int
        var s myslice = []int{1, 2, 3}
        var s2 []int = s
    
        fmt.Println(b2)
        fmt.Println(s2)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 1234                                           //
    // [1 2 3]                                        //
    ////////////////////////////////////////////////////
    

第二章 表达式

保留字

  • 所有保留字如下
    break        default        func
    case         defer          go
    chan         else           goto
    const        fallthrough    if
    continue     for            import
    interface      select
    map            struct
    package        switch
    range          type
    return         var
    

运算符

  • 几个常规的运算符.需要特别注意x$^y,其意义是把x中的某些bit上的值,值为零(无论 原来是1是0), 至于哪些位置y中所有为1,是由y中有哪些位置为1来决定的
    package main
    
    import "fmt"
    
    func main() {
        a := 0x011
        b := 0x101
        fmt.println(a & b)
        fmt.println(0x0001)
    
        fmt.println(a | b)
        fmt.println(0x0111)
    
        fmt.println(a ^ b)
        fmt.println(0x0110)
    
        // clear 1 to 0 on 0th and 3th from 0x101 for 0x011
        fmt.println(a &^ b)
        fmt.println(0x010)
    }
    
    ////////////////////////////////////////////////////
    // <===================output===================> //
    // 1                                              //
    // 1                                              //
    // 273                                            //
    // 273                                            //
    // 272                                            //
    // 272                                            //
    // 16                                             //
    // 16                                             //
    ////////////////////////////////////////////////////
    
  • 不支持运算符重载
  • 尤其要注意的是"++", "–"是语句而非表达式,这样做的原因,是为了防止一些低级错误
    n := 0
    p := &n
    // b := n++                     // syntax error
    // if n++ == 1 {}               // syntax error
    // ++n                          // syntax error
    
    n++
    *p++                            // (*p)++
    
  • 没有"~",取反也用"^"
    package main
    
    import "fmt"
    
    func main() {
        x := 1
        fmt.println(x)
        fmt.println(^x)
    }
    
    ////////////////////////////////////////////////////
    // <===================output===================> //
    // 1                                              //
    // -2                                             //
    ////////////////////////////////////////////////////
    

初始化

  • 初始化复合类型,必须使用type,左括号必须在类型的尾部,而不是另起一行
    package main
    
    import "fmt"
    
    func main() {
        a := struct{ x int }{100}
        b := []int{1, 2, 3}
    
        fmt.println(a)
        fmt.println(b)
    }
    
    ////////////////////////////////////////////////////
    // <===================output===================> //
    // {100}                                          //
    // [1 2 3]                                        //
    ////////////////////////////////////////////////////
    
  • 初始化以','分割,可以分几行,但是最后一行必须是以','或者'}'结尾,这里可以放心 因为gofmt会帮助你.

控制流

if

  • if的写法和c有如下区别:
    • 条件表达式括号省略了
    • 可以定义局部变量
    • 左括号必须在最后
  • 注意!不支持三元操作符 "a > b ? a : b"

for (while)

  • go中只有for,没有while:
    • 常见的for循环,各种语言都有
      s := "abc"
      
      for i, n := 0, len(s); i < n; i++ {
          fmt.println(s[i])
      }
      
    • 替代while (n > 0)
      n := len(s)
      for n > 0 {
          fmt.println(s[n])
          n--
      }
      
    • 替代while(true)
      for {
          fmt.println(s)
      }
      

range

  • range操作的核心是返回(key,value), (key, value)并不是只有map有,其他的几种类 型如果能使用range的话,也有其`key`和`value`
      1st value (key) 2nd value(value)
    string index s[index]
    array/slice index s[index]
    map key value
    channel element  
  • range的使用方法如下, 不想使用的部分使用`_`
    package main
    
    import "fmt"
    
    func main() {
        // string/array/slice they are the same
        s := "abc"
        for i, j := range s {
            fmt.printf("%d  %c\n", i, j)
        }
    
        for _, c := range s {
            fmt.println(c)
        }
    
        //map
        m := map[string]int{"a": 1, "b": 2}
        for k, v := range m {
            fmt.println(k, v)
        }
    }
    
    ////////////////////////////////////////////////////
    // <===================output===================> //
    // 0  a                                           //
    // 1  b                                           //
    // 2  c                                           //
    // 97                                             //
    // 98                                             //
    // 99                                             //
    // a 1                                            //
    // b 2                                            //
    ////////////////////////////////////////////////////
    
  • range会涉及到"值拷贝",也就是说:
    • 如果range所处理的类型是值, 那么key, value返回的都是copy value,典型的例子 就是array,下面的例子中`a`虽然已经开始被置为999,但是因为其"原始值"1已经 在range调用那一刻被拷贝出来了,所以a= v + 100的时候, v还是1
      package main
      
      import "fmt"
      
      func main() {
          // array is not reference type, everytime range will
          // copy the value to i and v
          a := [3]int{0, 1, 2}
          for i, v := range a {
              if i == 0 {
                  a[1], a[2] = 999, 999
                  fmt.println(a) // change a's value succesfully
              }
              a[i] = v + 100 // use a's copied value to construct the final version
      
          }
          fmt.println(a)
      }
      
      ////////////////////////////////////////////////////
      // <===================output===================> //
      // [0 999 999]                                    //
      // [100 101 102]                                  //
      ////////////////////////////////////////////////////
      
    • 所以我们最好让range处理引用类型的容器,array的好基友slice就是引用类型的 "数组", 下面的例子中
      package main
      
      import "fmt"
      
      func main() {
          // slice here is reference type, no copy happened when ranging
          s := []int{1, 2, 3, 4, 5}
      
          for i, v := range s {
              if i == 0 {
                  // change s's size will not deduced the iteration times
                  s = s[:3]
                  s[2] = 100
              }
      
              fmt.println(i, v) // s[2] will changed
          }
          fmt.println(s) // s[2] will changed
      }
      
      ////////////////////////////////////////////////////
      // <===================output===================> //
      // 0 1                                            //
      // 1 2                                            //
      // 2 100                                          //
      // 3 4                                            //
      // 4 5                                            //
      // [1 2 100]                                      //
      ////////////////////////////////////////////////////
      

switch

  • switch表达式可以是任意类型,不限于常量:
    • 可以省略break,默认自动终止
      package main
      
      import "fmt"
      
      func main() {
          x := []int{1, 2, 3}
          i := 2
      
          switch i {
          case x[1]:
              fmt.println("a")
          case 1, 3:
              fmt.println("b")
          default:
              fmt.println("c")
          }
      }
      
      ////////////////////////////////////////////////////
      // <===================output===================> //
      // a                                              //
      ////////////////////////////////////////////////////
      
    • 可以继续下一个(不是全部)分支,使用fallthrough, 但是条件不再判断(也就是自 动运行下一个分支)
      package main
      
      import "fmt"
      
      func main() {
          x := 10
          switch x {
          case 10:
              fmt.println("a")
              fallthrough
          case 0:
              fmt.println("b")
          case -1:
              fmt.println("c")
          }
      }
      
      ////////////////////////////////////////////////////
      // <===================output===================> //
      // a                                              //
      // b                                              //
      ////////////////////////////////////////////////////
      
  • switch省略条件表达式,或者把条件表达式作为初始化条件,可以把switch作为if elseif else来用
    switch {
    case x[1] > 0:
        fmt.println("a")
    case x[1] < 0:
        fmt.println("b")
    default:
        fmt.println("c")
    }
    

goto, break, continue

  • goto和c里面差不多,只不过标签必须使用大写
    package main
    
    import "fmt"
    
    func main() {
    
        var i int
        for {
            fmt.println(i)
            i++
            if i > 2 {
                goto break
            }
        }
    break:
        fmt.println("break")
    }
    
    ////////////////////////////////////////////////////
    // <===================output===================> //
    // 0                                              //
    // 1                                              //
    // 2                                              //
    // break                                          //
    ////////////////////////////////////////////////////
    
  • break用于在for, switch, select里面跳出当前循环, continue是进入下一次循环, 只能用于for循环

第三章 函数

函数定义

  • bgo首先去掉了几个不太实用的函数feature:
    • 不支持嵌套(nested)
    • 不支持重载(overload)
    • 不支持默认参数(default parameter)
  • go函数有如下特点:
    • 不需要声明原形
    • 支持不定长变参数
    • 支持多返回值
    • 支持命名返回参数
    • 支持匿名函数和闭包
  • 使用func定义函数,格式你懂的
    func test(x, y int, s string) (int, string) {
        n := x + y
        return n, fmt.sprintf(s, n)
    }
    
  • 函数是first-class 对象.可以作为参数传递.可以使用type将复杂函数定义为函数类型
    package main
    
    import "fmt"
    
    func test(fn func() int) int {
        return fn()
    }
    
    type formatfunc func(s string, x, y int) string
    
    func format(fn formatfunc, s string, x, y int) string {
        return fn(s, x, y)
    }
    
    func main() {
        s1 := test(func() int { return 100 })
        s2 := format(func(s string, x, y int) string {
            return fmt.sprintf(s, x, y)
        }, "%d, %d", 10, 20)
    
        fmt.println(s1)
        fmt.println(s2)
    }
    
    ////////////////////////////////////////////////////
    // <===================output===================> //
    // 100                                            //
    // 10, 20                                         //
    ////////////////////////////////////////////////////
    

变参类型

  • 变参其实就是最后一个参数是slice(只能有一个),而且使用slice变量作为参数的时候, 得写成'x…'
    package main
    
    import "fmt"
    
    func test(s string, n ...int) string {
        var x int
        for _, i := range n {
            x += i
        }
        return fmt.sprintf(s, x)
    }
    
    func main() {
    
        fmt.println(test("sum: %d", 1, 2, 3))
        s := []int{1, 2, 3, 4, 5}
        fmt.println(test("sum: %d", s...))
    }
    
    ////////////////////////////////////////////////////
    // <===================output===================> //
    // sum: 6                                         //
    // sum: 15                                        //
    ////////////////////////////////////////////////////
    

返回值

  • 不能使用容器接收多返回值,只能使用多个变量.不像要的参数使用"_"忽略
    package main
    
    import "fmt"
    
    func test() (int, int) {
        return 1, 2
    }
    
    func main() {
        // error => multiple-value test() in single-value context
        // s := make([]int, 2)
        // s = test()
        x, _ := test()
        fmt.println(x)
    }
    
    ////////////////////////////////////////////////////
    // <===================output===================> //
    // 1                                              //
    ////////////////////////////////////////////////////
    
  • 多返回值作为实参:
    • 理所当然可以看成是slice,所以可以作为n…类型的实参.
      package main
      
      import "fmt"
      
      func test() (int, int) {
          return 1, 2
      }
      
      func sum(n ...int) int {
          var x int
      
          for _, i := range n {
              x += i
          }
      
          return x
      }
      
      func main() {
          fmt.println(sum(test()))
      }
      
      ////////////////////////////////////////////////////
      // <===================output===================> //
      // 3                                              //
      ////////////////////////////////////////////////////
      
    • 参数有x,y int类型的情况下,也可以使用多返回值作为实参
      package main
      
      import "fmt"
      
      func test() (int, int) {
          return 1, 2
      }
      
      func add(x, y int) int {
          return x + y
      }
      
      func main() {
          fmt.println(add(test()))
      }
      
      ////////////////////////////////////////////////////
      // <===================output===================> //
      // 3                                              //
      ////////////////////////////////////////////////////
      
  • 如果返回值写"名字",那么就等同于设置了这个"名字"的局部变量,return后面啥都没 有的话,就是隐式返回.
    package main
    
    import "fmt"
    
    func add(x, y int) (z int) {
        z = x + y
        return
    }
    
    func main() {
        fmt.println(add(1, 2))
    }
    
    ////////////////////////////////////////////////////
    // <===================output===================> //
    // 3                                              //
    ////////////////////////////////////////////////////
    

匿名函数

  • 匿名函数也是神通广大,可以:
    • 赋值给变量:
      package main
      
      import "fmt"
      
      func main() {
      
          fns := [](func(x int) int){
              func(x int) int { return x + 1 },
              func(x int) int { return x + 2 },
          }
      
          fmt.println(fns[0](100))
      }
      
      ////////////////////////////////////////////////////
      // <===================output===================> //
      // 101                                            //
      ////////////////////////////////////////////////////
      
    • 作为结构字段:
      package main
      
      import "fmt"
      
      func main() {
          d := struct {
              fn func() string
          }{
              fn: func() string { return "hello, world" },
          }
      
          fmt.println(d.fn())
      }
      
      ////////////////////////////////////////////////////
      // <===================output===================> //
      // hello, world                                   //
      ////////////////////////////////////////////////////
      
    • 在channel里面传送:
      package main
      
      import "fmt"
      
      func main() {
          fc := make(chan func() string, 2)
          fc <- func() string { return "hello world" }
          fmt.println((<-fc)())
      }
      
      ////////////////////////////////////////////////////
      // <===================output===================> //
      // hello world                                    //
      ////////////////////////////////////////////////////
      

延迟调用

  • 关键字defer用于注册延迟调用,这些调用知道return之前才执行,通常用于释放资源或 错误处理
    package main
    
    import (
        "fmt"
        "os"
    )
    
    func test() error {
        f, err := os.Create("test.txt")
    
        if err != nil {
            return err
        }
    
        defer f.Close()
        f.WriteString("Hello World")
        return nil
    }
    
    func main() {
        err := test()
        if err != nil {
            fmt.Println("Error")
        }
    }
    
  • 可以使用下面的方法"延迟读取"
    package main
    
    import "fmt"
    
    func test() {
        x, y := 10, 20
    
        // take x as parameter for anoymous function, saved x's value
        // as 10.
        defer func(i int) {
            fmt.Println("defer:", i, y)
        }(x)
    
        x += 10
        y += 100
        fmt.Println("x = ", x, "y = ", y)
    }
    
    func main() {
        test()
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // x =  20 y =  120                               //
    // defer: 10 120                                  //
    ////////////////////////////////////////////////////
    

错误处理

  • 没有结构化异常, 使用panic抛出错误,然后使用recover捕获错误
    package main
    
    import "fmt"
    
    func test() {
        defer func() {
            if err := recover(); err != nil {
                fmt.Println(err.(string)) // err is interface{} type and now convert it to string
            }
        }()
    
        panic("panic error!")
    }
    
    func main() {
        test()
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // panic error!                                   //
    ////////////////////////////////////////////////////
    
  • 上面有err转化成string的部分,因为panic, recover的参数都是interface{}类型,因 此可以抛出任何类型的对象
    func panic(v interface{})
    func recover() interface{}
    
  • 延迟调用中引发的错误,可以被后续延迟调用捕获,但仅最后一个错误可以被捕获
    package main
    
    import "fmt"
    
    func test() {
        defer func() {
            fmt.Println(recover())
        }()
    
        defer func() {
            panic("defer panic")
        }()
    
        panic("test panic")
    }
    
    func main() {
        test()
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // defer panic                                    //
    ////////////////////////////////////////////////////
    
  • 捕获函数recover必须在两个地方:
    • 匿名函数:
      package main
      
      func test() {
          defer func() {
              recover()
          }()
          panic("test panic")
      }
      
      func main() {
          test()
      }
      
    • 函数体内才能起作用.
      package main
      
      func except() {
          recover()
      }
      
      func test() {
          defer except()
          panic("test panic")
      }
      
      func main() {
          test()
      }
      
  • 其他方式(不在函数里面,或者是两层函数),总是失败,recover()会返回nil
    package main
    
    import "fmt"
    
    func test() {
        defer recover()
        defer fmt.Println(recover())
    
        defer func() {
            func() {
                fmt.Println("defer inner")
                recover()
            }()
        }()
    
        panic("test panic")
    }
    
    func main() {
        test()
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // defer inner                                    //
    // <nil>                                          //
    // panic: test panic                              //
    ////////////////////////////////////////////////////
    
  • 除了使用panic引发中断性错误外,还可以使用error类型来返回错误,error类型其实是 一个接口,定义如下
    type error interface {
        Error() string
    }
    
  • 我们可以使用errors.New来创建一个error类型的instance,使用方法如下
    package main
    
    import "fmt"
    import "errors"
    
    var ErrDivByZero = errors.New("hfeng: division by zero")
    
    func div(x, y int) (int, error) {
        if y == 0 {
            return 0, ErrDivByZero
        }
        return x / y, nil
    }
    
    func main() {
        switch z, err := div(10, 0); err {
        case nil:
            fmt.Println(z)
        case ErrDivByZero:
            panic(err)
        }
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // panic: hfeng: division by zero                 //
    ////////////////////////////////////////////////////
    

第四章 数据

Array

  • 数组和以往认知的数组有很大的不同:
    • 数组是值类型,赋值和传参数会复制整个数组,而不是指针(所以有时候传递数组给函 数的话,代价很大)
    • 数组长度必须是常量,且是类型的组成部分.int和int是不同类型
    • 支持"==", "!="操作符
    • 支持数组[n]*T, 数组指针*[n]T (这里看出类型在后的好处了)
  • 和c语言相比,go的数组初始化方法非常的多样化
    package main
    
    import "fmt"
    
    func main() {
        a := [3]int{1, 2}
        b := [...]int{1, 2, 3, 4}
        c := [5]int{2: 100, 4: 200}
    
        d := [...]struct {
            name string
            age  uint8
        }{
            {"user1", 10},
            {"user2", 20},
        }
    
        fmt.Println(a)
        fmt.Println(b)
        fmt.Println(c)
        fmt.Println(d)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [1 2 0]                                        //
    // [1 2 3 4]                                      //
    // [0 0 100 0 200]                                //
    // [{user1 10} {user2 20}]                        //
    ////////////////////////////////////////////////////
    
  • 多维数组初始化方法如下, 需要注意的是,只支持第一维度的设置为"…"
    package main
    
    import "fmt"
    
    func main() {
        a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
        b := [...][2]int{{1, 1}, {2, 2}, {3, 3}}
    
        fmt.Println(a)
        fmt.Println(b)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [[1 2 3] [4 5 6]]                              //
    // [[1 1] [2 2] [3 3]]                            //
    ////////////////////////////////////////////////////
    
  • 值拷贝行为会造成性能的下降,因为会复制整个数组
    package main
    
    import "fmt"
    
    func test(x [2]int) {
        fmt.Printf("x: %p\n", &x)
        x[1] = 1000
    }
    
    func main() {
        a := [2]int{}
        fmt.Printf("a: %p\n", &a)
    
        test(a)
        fmt.Println(a)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // a: 0xc082008310                                //
    // x: 0xc082008350                                //
    // [0 0]                                          //
    ////////////////////////////////////////////////////
    
  • 内置的len和cap对于数组来说都是返回数组长度(元素数量),因为不可能有空间而不放 数据
    package main
    
    import "fmt"
    
    func main() {
        a := [2]int{}
        fmt.Println(len(a), cap(a))
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 2 2                                            //
    ////////////////////////////////////////////////////
    

Slice

  • slice是go语言对于array的一种补充,因为array是值传递的,为了减少值传递作为参数 等情况下带来的性能问题,go特地发明了array的引用类型"替身"–slice
  • slice在go的源代码里面其实就是1指针数组2长度3容量的一个组合
    struct Slice
    {
        byte* array;
        uintgo len;
        uintgo cap;
    };
    
  • 我们通过下面一个例子来看看slice和其"被绑定"数组的关系:
    • 先看源代码:
      data := [...]int{0, 1, 2, 3, 4, 5, 6}
      slice := data[1:4:5]            // [low:high:max]
      
    • 再来看看两者的关系(图例)
                    +- low   high -+    +- max                                   len = high - low
                    |              |    |                                        cap = max  - low
            +---+---+---+---+---+---+---+           +---------+---------+---------+
      data  | 0 | 1 | 2 | 3 | 4 | 5 | 6 |    slice  | pointer | len = 3 | cap = 4 |
            +---+---+---+---+---+---+---+           +---------+---------+---------+
                    |<--- len ---->|    |                              |
                    |                   |                              |
                    |<----- cap ------->|                              |
                    |                                                  |
                    +-------<<<-------- slice.array pointer ---<<<-----+
      
  • 使用[low:high:max]方法从已有数组创建slice的情况,不一定每次都全面的提供low, high, max.使用默认值的情况总计如下:
    • 代码
      data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
      
    • 从data创建slice的各种默认值组合
      expression slice len cap comment
      data[:6:8] [0 1 2 3 4 5] 6 8 low is array's low
      data[5:] [5 6 7 8 9] 5 5 high is array's high, max is array{low, high}
      data[:3] [0 1 2] 3 10 low is array's low, max is array{low, high}
      data[:] [0 1 2 3 4 5 6 7 8 9] 10 10  
  • 如果slice是从某个数组创建的话,那么对于slice的读写操作其实就是对于底层数组的 读写
    package main
    
    import "fmt"
    
    func main() {
        data := [...]int{0, 1, 2, 3, 4, 5}
    
        s := data[2:4]
        s[0] += 100
        s[1] += 200
    
        fmt.Println(s)
        fmt.Println(data)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [102 203]                                      //
    // [0 1 102 203 4 5]                              //
    ////////////////////////////////////////////////////
    
  • slice如果创建的时候还要带个拖油瓶的"数组"那用起来太麻烦了,go还是可以"直接创 建"slice的
    package main
    
    import "fmt"
    
    func main() {
        s1 := []int{0, 1, 2, 3, 8: 100} // Note! there are nothing in []
        fmt.Println(s1, len(s1), cap(s1))
    
        s2 := make([]int, 6, 8) // make to create, len and cap as parameter
        fmt.Println(s2, len(s1), cap(s1))
    
        s3 := make([]int, 6) // len == cap here
        fmt.Println(s3, len(s3), cap(s3))
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [0 1 2 3 0 0 0 0 100] 9 9                      //
    // [0 0 0 0 0 0] 9 9                              //
    // [0 0 0 0 0 0] 6 6                              //
    ////////////////////////////////////////////////////
    
  • 直接创建slice是非常"经济"的一件事情,因为大部分情况下都是用slice的,实在想使 用数组还可以使用指针来直接访问数组
    package main
    
    import "fmt"
    
    func main() {
        s := []int{0, 1, 2, 3}
    
        p := &s[2] // *int, got underlying array pointer
        *p += 100
    
        fmt.Println(s)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [0 1 102 3]                                    //
    ////////////////////////////////////////////////////
    
  • 我们看到了[]里面没有东西是slice的特点,所以[][]T的类型是'[]T的数组'
    package main
    
    import "fmt"
    
    func main() {
        data := [][]int{
            []int{1, 2, 3},
            []int{100, 200},
            []int{11, 22, 33, 44},
        }
        fmt.Println(data)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [[1 2 3] [100 200] [11 22 33 44]]              //
    ////////////////////////////////////////////////////
    
  • 可以直接更改struct数组(或slice)的内部成员
    package main
    
    import "fmt"
    
    func main() {
        d := [5]struct {
            x int
        }{}
    
        s := d[:]
    
        d[1].x = 10
        s[2].x = 20
    
        fmt.Println(d)
        fmt.Printf("%p, %p\n", &d, &d[0])
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [{0} {10} {20} {0} {0}]                        //
    // 0xc082003f80, 0xc082003f80                     //
    ////////////////////////////////////////////////////
    

reslice

  • 所谓reslice是基于已有slice创建新的slice对象.以便在cap允许范围内调整属性,每 次获得新的slice,其len,cap等属性都会变化:
    • 代码如下:每次创建的新slice其[low,high,max]都会有所改动.
      package main
      
      import "fmt"
      
      func main() {
          s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
          fmt.Println(s, len(s), cap(s))
      
          s1 := s[2:5]
          fmt.Println(s1, len(s1), cap(s1))
          s2 := s1[2:6:7]
          fmt.Println(s2, len(s2), cap(s2))
          // s3 := s2[3:6]                    // Error out of range
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // [0 1 2 3 4 5 6 7 8 9] 10 10                    //
      // [2 3 4] 3 8                                    //
      // [4 5 6 7] 4 5                                  //
      ////////////////////////////////////////////////////
      
    • 图例如下最后s2已经变成4,5,6,7, size为4, cap为7了,所以你的s3超过了我s2的cap
           +-+-+-+-+-+-+-+-+-+-+
      data |0|1|2|3|4|5|6|7|8|9|
           +-+-+-+-+-+-+-+-+-+-+
               +-+-+-+-+-+-+-+-+
      s1       |2|3|4| | | | | | len = 3, cap = 8
               +-+-+-+-+-+-+-+-+
                   +-+-+-+-+-+
      s2           |4|5|6|7| |   len = 4, cap = 5
                   +-+-+-+-+-+
                         +-+-+-+
      s3                 |7|8|X| error: slice bounds out of range
                         +-+-+-+
      
  • 新的对象,依然是指向底层数组
    package main
    
    import "fmt"
    
    func main() {
        s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    
        s1 := s[2:5]
        s1[2] = 100
    
        s2 := s1[2:6]
        s2[3] = 200
    
        fmt.Println(s)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [0 1 2 3 100 5 6 200 8 9]                      //
    ////////////////////////////////////////////////////
    

append

  • 向slice尾部添加数据,返回新的slice对象,简单点说,就是在array[high..cap]之间 写数据
    package main
    
    import "fmt"
    
    func main() {
        data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
        s := data[:3]
        s2 := append(s, 100, 200)
    
        fmt.Println(data)
        fmt.Println(s)
        fmt.Println(s2)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [0 1 2 100 200 5 6 7 8 9]                      //
    // [0 1 2]                                        //
    // [0 1 2 100 200]                                //
    ////////////////////////////////////////////////////
    
  • 我们也很容易想到,如果超过cap的限制,那么肯定不能再使用原来的数组了,而是要申 请新的数组,当然,这个过程是自动的
    package main
    
    import "fmt"
    
    func main() {
        data := [...]int{0, 1, 2, 3, 4, 10: 0}
        s := data[:2:3]
    
        s = append(s, 100, 200) // append two, exceed cap
        fmt.Println(s, data)
        fmt.Println(&s[0], &data[0])
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0]          //
    // 0x8201ea210 0x8201ee180                        //
    ////////////////////////////////////////////////////
    

copy

  • 函数copy在两个slice之间复制数据.其实就是把srcSlice的底层数组保存一份到 destSlice的底层数组上,然后srcSlice就可以释放了
    package main
    
    import "fmt"
    
    func main() {
        data1 := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
        s1 := data1[:]
        fmt.Println(s1)
        data2 := [...]int{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
        s2 := data2[:]
        fmt.Println(s2)
    
        copy(s1, s2) // copy s1 from s2
    
        fmt.Println(s1)
        fmt.Println(s2)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // [0 1 2 3 4 5 6 7 8 9]                          //
    // [10 11 12 13 14 15 16 17 18 19 20]             //
    // [10 11 12 13 14 15 16 17 18 19]                //
    // [10 11 12 13 14 15 16 17 18 19 20]             //
    ////////////////////////////////////////////////////
    

Map

  • map是引用类型.键是支持(==, !=)的类型(比如number, string, pointer, array, struct), 值可以是任意类型
    package main
    
    import "fmt"
    
    func main() {
        m := map[int]struct {
            name string
            age  int
        }{
            1: {"user1", 10},
            2: {"user2", 20},
        }
    
        fmt.Println(m[1].name)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // user1                                          //
    ////////////////////////////////////////////////////
    
  • 预先给make函数一个合理的元素数量参数,有助于提高性能,因为这会事先分配一大块 内存,而不是后续频繁扩张
    m := make(map[string]int, 1000)
    
  • 常见的查询(注意m["a"]是返回两个值的,不过通常不用第二个返回值error),删除等操 作如下
    package main
    
    import "fmt"
    
    func main() {
        m := map[string]int{
            "a": 1,
        }
    
        if v, ok := m["a"]; ok { // m["a"] will return two value!
            fmt.Println(v)
        }
    
        fmt.Println(m["c"])
    
        m["b"] = 2
        delete(m, "c")
        fmt.Println(len(m))
    
        for k, v := range m {
            fmt.Println(k, v)
        }
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 1                                              //
    // 0                                              //
    // 2                                              //
    // a 1                                            //
    // b 2                                            //
    ////////////////////////////////////////////////////
    
  • 从map中取回的value是一个临时值,所以对其成员的修改是没有意义的, 所以更改map 内容的方法看起来有些奇怪
    package main
    
    import "fmt"
    
    func main() {
        type user struct{ name string }
    
        m := map[int]user{
            1: {"user1"},
        }
    
        fmt.Println(m)
    
        // m[1].name = "Tom"            // cannot assign to m[1].name
        u := m[1]
        u.name = "Tom"
        m[1] = u
        fmt.Println(m)
    
        m2 := map[int]*user{
            1: &user{"user1"},
        }
        fmt.Println(m2[1])
    
        m2[1].name = "Jack"
    
        fmt.Println(m2[1])
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // map[1:{user1}]                                 //
    // map[1:{Tom}]                                   //
    // &{user1}                                       //
    // &{Jack}                                        //
    ////////////////////////////////////////////////////
    

Struct

  • struct也是值类型,赋值和传参数都会复制全部内容.可用'_'定义补位字段
    package main
    
    import "fmt"
    
    type Node struct {
        _    int
        id   int
        data *byte
        next *Node
    }
    
    func main() {
        n1 := Node{
            id:   1,
            data: nil,
        }
    
        n2 := Node{
            id:   2,
            data: nil,
            next: &n1,
        }
    
        fmt.Println(n1)
        fmt.Println(n2)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // {0 1 <nil> <nil>}                              //
    // {0 2 <nil> 0x82024a020}                        //
    ////////////////////////////////////////////////////
    
  • 初始化的时候,要包含全部的成员
    package main
    
    import "fmt"
    
    type User struct {
        name string
        age  int
    }
    
    func main() {
        u1 := User{"Tom", 20}
        fmt.Println(u1)
    
        // u2 := User{"Tom"} // too few values in struct initializer
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // {Tom 20}                                       //
    ////////////////////////////////////////////////////
    
  • struct里面还可以有匿名struct
    package main
    
    import "fmt"
    
    type File struct {
        name string
        size int
        attr struct {
            perm int
            ower int
        }
    }
    
    func main() {
        f := File{
            name: "test.txt",
            size: 1025,
        }
    
        var attr = struct {
            perm int
            ower int
        }{2, 0755}
    
        fmt.Println(f)
        f.attr = attr
        fmt.Println(f)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // {test.txt 1025 {0 0}}                          //
    // {test.txt 1025 {2 493}}                        //
    ////////////////////////////////////////////////////
    

匿名字段

  • 匿名字段,就是某个struct里面的字段和"另外一个类型名字相同(可以是任意类型)"
    package main
    
    import "fmt"
    
    type User struct {
        name string
    }
    
    type Manager struct {
        User
        title string
    }
    
    func main() {
    
        m := Manager{
            User:  User{"Tom"},
            title: "Administrator",
        }
    
        fmt.Println(m)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // {{Tom} Administrator}                          //
    ////////////////////////////////////////////////////
    
  • 嵌套匿名类,访问起来,就是逐级访问
    package main
    
    import "fmt"
    
    type Resource struct {
        id   int
        name string
    }
    
    type Classify struct {
        id int
    }
    
    type User struct {
        Resource
        Classify
        name string
    }
    
    func main() {
        u := User{
            Resource{1, "people"},
            Classify{100},
            "Jack",
        }
    
        fmt.Println(u.name)
        fmt.Println(u.Resource.name)
        // fmt.Println(u.id)            //  ambiguous selector u.id
        fmt.Println(u.Classify.id)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Jack                                           //
    // people                                         //
    // 100                                            //
    ////////////////////////////////////////////////////
    
  • 不能同时嵌套同一类型和其指针类型,因为他们名字相同
    type Resource struct {
        id int
    }
    
    type User struct {
        *Resource
        // Resource // Error: duplicate field Resource
        name string
    }
    

面向对象

  • 面向对象的三大特征(继承,多肽,封装)里面, GO仅仅支持封装
  • 尽管匿名字段的内存布局和行为特征,类似继承,但是没有class关键字,也没有多肽,继承
    package main
    
    import "fmt"
    
    type User struct {
        id   int
        name string
    }
    
    type Manager struct {
        User
        title string
    }
    
    func main() {
    
        m := Manager{User{1, "Tom"}, "Administrator"}
        fmt.Println(m)
        // cannot use m (type Manager) as type User in assignment,
        // no Polymorphism here
        // var u User = m
    
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // {{1 Tom} Administrator}                        //
    ////////////////////////////////////////////////////
    

第五章 方法

方法定义

  • 方法总是绑定对象实例,并将这个实例作为第一实际参数(receiver)
  • 方法有其如下特点:
    • 只能为当前包内存在的命名类型定义方法
    • 参数receiver可任意命名,如方法中没有使用这个receiver,也可以只定义不给名字
    • 参数receiver可以是类型T或是T* (其中T不能是接口或者指针)
    • 不支持方法重载
    • 可用实例value或者pointer调用全部方法,编译器自动转换
  • go没有ctor和dtor,一般使用"简单工厂模式"返回对象实例
    package main
    
    import "fmt"
    
    type Queue struct {
        elements []interface{}
    }
    
    func NewQueue() *Queue {
        return &Queue{make([]interface{}, 10)}
    }
    
    func (*Queue) Push(e interface{}) error {
        panic("not implemented")
    }
    
    func (self *Queue) length() int {
        return len(self.elements)
    }
    
    func main() {
        q := NewQueue()
        fmt.Printf("%d\n", q.length())
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 10                                             //
    ////////////////////////////////////////////////////
    
  • 方法其实就是"第一个参数为receiver的函数", 而且receiver为T的时候是copy by value 而receiver为T*的时候,是copy by reference
    package main
    
    import "fmt"
    
    type Data struct {
        x int
    }
    
    func (self Data) ValueTest() {
        // Pass by Value, &self is changing
        fmt.Printf("Value: %p\n", &self)
    }
    
    func (self *Data) PointerTest() {
        // Pass by reference, self is always the same
        fmt.Printf("Pointer: %p\n", self)
    }
    
    func main() {
        d := Data{}
        p := &d
        fmt.Printf("Data: %p\n", p)
    
        d.ValueTest()
        d.PointerTest()
    
        p.ValueTest()
        p.PointerTest()
    
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Data: 0x82024c220                              //
    // Value: 0x82024c240                             //
    // Pointer: 0x82024c220                           //
    // Value: 0x82024c250                             //
    // Pointer: 0x82024c220                           //
    ////////////////////////////////////////////////////
    

匿名字段

  • 一个struct内部有匿名字段的话,这个struct可以直接访问这个匿名字段的函数,就像 访问自己的函数一样.(用组合的方式实现了继承)
    package main
    
    import "fmt"
    
    type User struct {
        id   int
        name string
    }
    
    type Manager struct {
        User
    }
    
    func (self *User) ToString() string {
        return fmt.Sprintf("User: %p, %v", self, self)
    }
    
    func main() {
        m := Manager{User{1, "Tom"}}
    
        fmt.Printf("Manager: %p\n", &m)
        fmt.Println(m.ToString())
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Manager: 0x820258000                           //
    // User: 0x820258000, &{1 Tom}                    //
    ////////////////////////////////////////////////////
    
  • 继承的好处当然就是可以override 函数,我们通过为两个不同的struct(两者有相互包 含的关系)定义名字相同的方法来实现了override
    package main
    
    import "fmt"
    
    type User struct {
        id   int
        name string
    }
    
    type Manager struct {
        User
        title string
    }
    
    func (self *User) ToString() string {
        return fmt.Sprintf("User: %p, %v", self, self)
    }
    
    func (self *Manager) ToString() string {
        return fmt.Sprintf("Manager: %p, %v", self, self)
    }
    
    func main() {
        m := Manager{User{1, "Tom"}, "Administrator"}
    
        fmt.Println(m.ToString())
        fmt.Println(m.User.ToString())
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Manager: 0x8201ec0c0, &{{1 Tom} Administrator} //
    // User: 0x8201ec0c0, &{1 Tom}                    //
    ////////////////////////////////////////////////////
    

方法集

  • 每个类型和其方法之间的关系:
    • 类型T方法集包含全部receiver T方法
    • 类型*T方法集包含全部receiver T + *T方法
    • 如果类型S包含匿名字段T,则S方法包含T 方法
    • 如果类型S包含匿名字段*T,则S方法包含T + *T方法
    • 不管嵌入T或者*T, *S方法总能包含T + *T方法

表达式

  • 方法有如下两种"等价"的表达方式
    • instance.method(args…)
    • <type>.func(instance, args)
  • 其实也很好理解,因为方法定义的时候,就和func一样(只不过前面多个type,可以是T也 可以是*T), 而调用的时候,则可以一来instance.method, 二来<T>.func(instance)
  • method的调用例子如下:
    • 使用*T的情况:
      package main
      
      import "fmt"
      
      type User struct {
      id   int
      name string
      }
      
      func (self *User) Test() {
      fmt.Printf("%p, %v\n", self, self)
      }
      
      func main() {
      u := User{1, "Tom"}
      u.Test()
      
      mValue := u.Test
      mValue()
      
      //use *T
      mExpression := (*User).Test
      //&u is the intance of (*T)
      mExpression(&u)
      
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // 0x8201e41a0, &{1 Tom}                          //
      // 0x8201e41a0, &{1 Tom}                          //
      // 0x8201e41a0, &{1 Tom}                          //
      ////////////////////////////////////////////////////
      
    • 使用T的情况, 我们可以看到这种情况下是pass-by-value
      package main
      
      import "fmt"
      
      type User struct {
          id   int
          name string
      }
      
      func (self User) Test() {
          fmt.Printf("%p, %v\n", &self, self)
      }
      
      func main() {
          u := User{2, "Jacky"}
          u.Test()
      
          mValue := u.Test
          mValue()
      
          mExpression := (User).Test
          mExpression(u)
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // 0x8201e41a0, {2 Jacky}                         //
      // 0x8201e4200, {2 Jacky}                         //
      // 0x8201e4240, {2 Jacky}                         //
      ////////////////////////////////////////////////////
      

第六章 接口

接口定义

  • 接口是一个或者多个方法签名的集合
  • 任何类型的方法只要拥有与接口A"相同的"全部方法,就表示它"实现"了这个接口.注意, 实现某接口并不需要在该类型上显示添加接口声明
  • 判断两个方法是否相同.主要是看:
    • 名称相同
    • 参数列表相同
    • 返回值相同
  • go里面的接口也有如下的特点:
    • 接口命名是以er结尾
    • 接口只有方法签名,没有实现
    • 接口没有数据字段
    • 接口中可以嵌入其他接口
    • 类型可以"实现"多个接口
  • 下面是一个接口的例子
    package main
    
    import "fmt"
    
    type Stringer interface {
        String() string
    }
    
    type Printer interface {
        Stringer
        Print()
    }
    
    type User struct {
        id   int
        name string
    }
    
    func (self *User) String() string {
        return fmt.Sprintf("user %d, %s", self.id, self.name)
    }
    
    func (self *User) Print() {
        fmt.Println(self.String())
    }
    
    func main() {
        var t Printer = &User{1, "Tom"}
        t.Print()
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // user 1, Tom                                    //
    ////////////////////////////////////////////////////
    
  • 空接口interface{}没有任何方法的签名,这也就意味着任何的类型都是实现了空接口. 其作用类似面向对象语言中的根对象object (也类似c语言里面的void*)
    package main
    
    import "fmt"
    
    func Print(v interface{}) {
        fmt.Printf("%T: %v\n", v, v)
    }
    
    func main() {
        Print(1)
        Print("Hello World!")
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // int: 1                                         //
    // string: Hello World!                           //
    ////////////////////////////////////////////////////
    
  • 匿名接口可以作为变量类型, 变量类型也可以是strcut的一部分, 下面例子中匿名 interface类型类型的变量s是Tester的唯一成员. 而User实现了这个interface的唯一 函数String(),那么就我们说s和User的类型是一样的
    package main
    
    import "fmt"
    
    type Tester struct {
        s interface {
            String() string
        }
    }
    
    type User struct {
        id   int
        name string
    }
    
    func (self *User) String() string {
        return fmt.Sprintf("user %d, %s", self.id, self.name)
    }
    
    func main() {
        t := Tester{&User{1, "Tom"}}
        fmt.Println(t.s.String())
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // user 1, Tom                                    //
    ////////////////////////////////////////////////////
    

执行机制

  • 接口对象由两个指针组成:
    • 接口返回临时对象, 数据指针持有的是目标对象的只读复制品,比如下例中的i其为 interface u直接返回的临时对象.更改原interface不会影响i
      package main
      
      import "fmt"
      
      type User struct {
          id   int
          name string
      }
      
      func main() {
          u := User{1, "Tom"}
          var i interface{} = u
      
          u.id = 2
          u.name = "Jack"
      
          fmt.Printf("%v\n", u)
          fmt.Printf("%v\n", i.(User))
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // {2 Jack}                                       //
      // {1 Tom}                                        //
      ////////////////////////////////////////////////////
      
    • 只有使用指针才能修改其状态
      package main
      
      import "fmt"
      
      type User struct {
          id   int
          name string
      }
      
      func main() {
          u := User{1, "Tom"}
          var vi, pi interface{} = u, &u
      
          // vi.(User).name = "Jack" // Error: cannot assign to vi.(User).name
          pi.(*User).name = "Jack"
      
          fmt.Printf("%v\n", vi.(User))
          fmt.Printf("%v\n", pi.(*User))
      }
      
      ////////////////////////////////////////////////////
      // <===================OUTPUT===================> //
      // {1 Tom}                                        //
      // &{1 Jack}                                      //
      ////////////////////////////////////////////////////
      

接口转换

  • 利用类型推断,可以判断某个对象是否"实现"了某个接口
    package main
    
    import "fmt"
    
    type User struct {
        id   int
        name string
    }
    
    func (self *User) String() string {
        return fmt.Sprintf("%d, %s", self.id, self.name)
    }
    
    func main() {
        var o interface{} = &User{1, "Tom"}
    
        //////////////////////////////////
        // Source Code for golang       //
        // type Stringer interface {    //
        //  String() string             //
        // }                            //
        //////////////////////////////////
    
        if i, ok := o.(fmt.Stringer); ok {
            fmt.Println(i)
        }
    
        u := o.(*User)
    
        // u := o.(User)  // panic: interface conversion: interface is *main.User, not main.User
        fmt.Println(u)
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 1, Tom                                         //
    // 1, Tom                                         //
    ////////////////////////////////////////////////////
    
  • 还可以使用switch做批量类型判断
    package main
    
    import "fmt"
    
    type User struct {
        id   int
        name string
    }
    
    func main() {
        var o interface{} = &User{1, "Tom"}
    
        switch v := o.(type) {
        case nil:
            fmt.Println("nil")
        case fmt.Stringer: // interface
            fmt.Println(v)
        case func() string: // func
            fmt.Println(v())
        case *User: // struct
            fmt.Printf("%d, %s\n", v.id, v.name)
        default:
            fmt.Println("unknown")
        }
    
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 1, Tom                                         //
    ////////////////////////////////////////////////////
    
  • 超集接口(父类)可以转换为子集接口(子类),这也是模仿OO的特性
    package main
    
    import "fmt"
    
    type Stringer interface {
        String() string
    }
    
    type Printer interface {
        String() string
        Print()
    }
    
    type User struct {
        id   int
        name string
    }
    
    func (self *User) String() string {
        return fmt.Sprintf("%d, %v", self.id, self.name)
    }
    
    func (self *User) Print() {
        fmt.Println(self.String())
    }
    
    func main() {
        var o Printer = &User{1, "Tom"}
        var s Stringer = o
        fmt.Println(s.String())
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // 1, Tom                                         //
    ////////////////////////////////////////////////////
    

接口检查

  • 可以使用如下的办法(转换nil 为Data类型的nil)来检查Data类型到底实现了Stringer没有
    package main
    
    import "fmt"
    
    type Data struct {
        id int
    }
    
    func (self Data) String() string {
        return ""
    }
    
    func main() {
        // fmt.Stringer is interface type, _ is variable, but we don't
        // care about it.
        var _ fmt.Stringer = (*Data)(nil)
    
        // IF String() is not implemented =>
        // *Data does not implement fmt.Stringer (missing String method)
    }
    
  • 还可以让函数"typedef"成type,然后来"实现"接口.很绕口的使用方法
    package main
    
    import "fmt"
    
    type Tester interface {
        Do()
    }
    
    // Regard function AS type
    type FuncDo func()
    
    // the function implement the interface!
    // then call itself!
    func (self FuncDo) Do() { self() }
    
    func main() {
        // t is Tester type
        // FuncDo implement the Do(), it is also the Tester type
        var t Tester = FuncDo(func() { fmt.Println("Hello World") })
        t.Do()
    }
    
    ////////////////////////////////////////////////////
    // <===================OUTPUT===================> //
    // Hello World                                    //
    ////////////////////////////////////////////////////