【Golang】小知识总结, 你中招了没有

  • 使用定时器

    t := time.NewTicker(1 * time.Second)
    
    // 第一种方式
    for {
        select {
        case <-t.C:
          fmt.Println("hello ")
        default:
          time.Sleep(100 * time.Millisecond)
        }
    }
    
    // 第二种方式
    for _ = range t.C {
      fmt.Println("hello")
    }
    
  • 生成指定长度的随机字符串

    rand.Seed(time.Now().UnixNano())
    
    data := make([]byte, 32)
    rand.Read(data)
    fmt.Println(hex.EncodeToString(data))
    
  • slice转array

    func main() {
      var arr [10]int
      s := []int{1, 2, 3, 4, 5}
      copy(arr[:], s)
    
      fmt.Println(arr)
    }
    

    原理解析:arr[:]是一个引用了底层数组arr的slice,长度和容量与数组的长度相同,也就是说这两个元素共用一块内存。所以当copy()时,改变了slice中的元素值,而数组的改变可以说是一种副效应(side-effect)。

  • 通过append函数删除slice中的元素,是否会重新分配内存的问题

    package main
    
    import "fmt"
    
    func main() {
      s := []int{1, 2, 3, 4, 5}
    
      a := s
      fmt.Println(a)
    
      s = append(s[:1], s[2:]...)
    
      // 修改s[0],如果a的数据也变化了,那么说明通过append()
      // 删除slice中的数据并没有重新分配内存空间
      s[0] = 12
      fmt.Println(a, s)
    }
    
    // output
    [1 2 3 4 5]
    [6 3 4 5 5] [6 3 4 5]
    
  • 交换两个元素的值,而不需要中间变量

    package main
    
    import "fmt"
    
    func main() {
      i := 1
      j := 2
       
      i = i ^ j
      j = i ^ j
      i = i ^ j
    
      fmt.Println(i, j)
    }
    
    // 规律是 i ^ i = 0
    
  • 如果当前函数或者其调用栈上游,有处理panic的recover(),panic()就不会输出,更不会终止程序,但是panic()的确执行了。

    package main
    
    import "fmt"
    
    func main() {
      test()
    
      fmt.Println("here is waiting")
    }
    
    func test() {
      defer func() {              // 可以在panic()上游的任何位置定义,都会拦截
          if r := recover(); r != nil {
              fmt.Println("is there")
          }
      }()
      base()
    }
    
    func base() {
      panic("this is a error")
    }
    
  • 从一个给定的slice获取空的slice

    package main
    
    import "fmt"
    
    func main() {
      s := []int{1, 2, 3, 4, 5}
      sEmpty := s[0:0]
      fmt.Println(sEmpty)
    }
    
  • []byte转十六进制

    b := []byte{12, 34, 89}
    
    // 以下会得到相同的输出
    fmt.Printf("%x\n", b)
    fmt.Println(hex.EncodeToString(b)
    
  • 大端法与小端法

    给定一个十六进制数0xOA0B0C0D,以下给出了在两种不同方法下内存的排序方式,内存地址从左向右依次增大。上方为小端法表示、下方为大端法表示。

    概念:

    • 大端法: 数字的高位和低位与内存地址相反
    • 小端法: 数字的高位和低位与内存地址相同

    比较好记的是大端法数字的书写顺序与内存排列顺序一致

    ![大端法、小端法][image-1]

    在golang中将一个数字转换成byte,在小端法中,会解析低内存地址的一个byte,作为输出.

    func main() {
      var a uint32 = 0x0A0B0C0D
    
      fmt.Println(byte(a))
    }
    
  • slice copy时注意事项

    第一种情况为空slice,因此在使用Copy()方法时,目标slice长度一定要指定为需要copy的长度

    func main() {
      s := make([]int, 0, 5)      // error
      //s := make([]int, 4)       // correct  4 or 5 ok
    
      c := []int{1, 2, 3, 4, 5}
      copy(s, c)
      fmt.Println(s)
    }
    
  • 标识符冲突

    同一个包内不能出现变量名、常量名、函数名两两冲突的情况,如下情况编译失败:

    package main
    
    import "fmt"
    
    const conflict = 38
    
    var conflict = 34
    
    func conflict() {
        fmt.Println(conflict)
    }
    
    func main() {
        fmt.Println(conflict)
        conflict()
    }
    
  • 求两个时间的间隔,会参数负数的情况

    func main() {
        t1 := time.Now()
        t2 := time.Now()
        fmt.Println(t1.Sub(t2)) // 负数
      
        t3 := time.Unix(math.MaxInt64, 0) // 属于越界的情况
        fmt.Println(t3.Sub(t1))
    }
     
    // output
    -657ns
    -2562047h47m16.854775808s
    

    所以在使用time包的Sub()函数时,要将结果与0进行比较
    now := time.Now()
    lastAttempt := now.Sub(ka.lastattempt) // 如果差值过大,就会形成负值

    if lastAttempt < 0 {
        lastAttempt = 0
    }
    
  • nil slice可以直接使用append()增加元素

        func main() {
            var b []byte
                 
            source := []byte{1, 2, 3}
            b = append(b, source...)
            fmt.Println(b)
        }
    
  • golang set循环控制及返回值

        func (ba *BlockAssembler) isStillDependent(te *mempool.TxMempoolEntry) bool {
            setParent := blockchain.GMemPool.GetMemPoolParents(te)
            ret := false
            setParent.Each(func(item interface{}) bool {
                if !ba.inBlock.Has(item.(*mempool.TxMempoolEntry)) {
                    ret = true          // control function result
                    return false        // control loop
                }
                return true             // control loop
            })
            return ret                  // return function result
        }
    
  • select默认是堵塞的,必须有一个分支执行才能出去。

        func main() {
            a := make(chan int, 1)
            c := make(chan int, 1)
            go func() {
                time.Sleep(1 * time.Second)
                a <- 1
                c <- 2
            }()
             
            // for next {
            select {
            case <-a:
                fmt.Println("a")
            case <-c:
                fmt.Println("c")
            }
        }
             
        // 如果所有通道中都没有内容,可以使用default语句,继续程序的执行。不过要看业务需求。
        func main() {
            a := make(chan int, 1)
            c := make(chan int, 1)
            go func() {
                time.Sleep(1 * time.Second)
                a <- 1
                c <- 2
            }()
             
            // for next {
            select {
            case <-a:
                fmt.Println("a")
            case <-c:
                fmt.Println("c")
            default:
                fmt.PrintLn("default")
            }
        }
    
  • switch结构中default语句的执行与其所在位置无关

    package main
    
    import "fmt"
    
    func main() {
        a := 232
    
        switch a {
        default:
            fmt.Println("default")
        case 1:
            fmt.Println(1)
        case 232:
            fmt.Println("ok")
        }
    }
    
  • 遍历一个无缓冲的channel,不会在从channel中读取一个元素就退出(for range 结构); for range 的终止条件是通道关闭,和channel有无缓冲无关。

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        c := make(chan int)
    
        go func() {
            i := 0
            for {
                c <- i
                i++
                time.Sleep(1 * time.Second)
            }
        }()
    
        go func() {
            for v := range c {
                fmt.Println(v)
            }
        }()
    
        time.Sleep(10 * time.Second)
        close(c)
    }
    
  • len()函数在channel的应用。当chanel为无缓冲的时候,len()在任何情况下都会返回0; 当channel为有缓冲的情况下,len()会返回当前缓冲中的元素个数,增大或减小取决于生产者和消费者的速度。总之,len()函数在channel中返回的是缓冲区的元素个数。

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        c := make(chan int, 5)      // 修改为无缓冲的channel尝试一下
    
        // producer
        go func() {
            i := 0
            for {
                c <- i
                i++
                time.Sleep(1 * time.Second)
            }
        }()
    
        // consumer
        go func() {
            for v := range c {
                fmt.Println(v)
                time.Sleep(2 * time.Second)
            }
        }()
    
        // watcher
        go func() {
            for {
                fmt.Println("channel length:", len(c))
                time.Sleep(500 * time.Millisecond)
            }
        }()
    
        time.Sleep(30 * time.Second)
    }
    
  • goto、continue、break语句实现代码跳转

    // 既可以往前跳,也可以往后跳
    func main() {
    previous:
        fmt.Println("previous")
        i := 0
        if i == 0 {
            goto next
        }
    next:
        fmt.Println("next")
        goto previous
    }
    
    // break跳转至指定标签后,将不执行标签对应的for循环。需要注意的是:标签只能定义在for循环之前。
    func main() {
        i := 0
    loop:
        for i <= 10 {
            i++
            fmt.Println("break")
            break loop
        }
        fmt.Println("finish, and i ==", i)
    }
    // output:
    break
    finish, and i == 1
    
    // continue跳转到指定标签后,如果for循环条件满足仍然会执行for循环,直到条件不满足为止。需要注意的是:标签只能定义在for循环之前。
    func main() {
        i := 0
    loop:
        for i <= 10 {
            i++
            fmt.Println("continue")
            continue loop
        }
        fmt.Println("finish, and i ==", i)
    }
    // output:
    continue
    continue
    continue
    continue
    continue
    continue
    continue
    continue
    continue
    continue
    continue
    finish, and i == 11
    
  • switch case for type asert:

    func main() {
        var i interface{}
        a := 3
        i = a
    
        switch t := i.(type) {
        case int:
            fmt.Println(t)
        default:
            fmt.Println("default")
        }
    }
    //output:
    3
    
  • <- c被触发的条件:

    1、通道中有数据

    2、通道被关闭

    func main() {
        c := make(chan struct{})
        //c := make(chan struct{}, 100)
        go func() {
            time.Sleep(5 * time.Second)
            close(c)
        }()
    
        for {
            if interruptRequest(c) {
                break
            }
            time.Sleep(1 * time.Second)
        }
    }
    
    func interruptRequest(interrupted <-chan struct{}) bool {
        select {
        case <-interrupted:
            fmt.Println("program is down....")
            return true
        default:
            fmt.Println("program is running...")
        }
        return false
    }
    
  • 函数内对slice作append,在函数外不可见: slice的元信息是SliceHeader,slice按值传递和按引用传递其实就是将SliceHeader按值传递或按指针传递。

    func main() {
        s := make([]int, 0)
        a := (*reflect.SliceHeader)(unsafe.Pointer(&s))
        fmt.Printf("%+v\n", a)
    
        test(s)
    
        b := (*reflect.SliceHeader)(unsafe.Pointer(&s))
        fmt.Printf("%+v\n", b)
    }
    
    func test(s []int) {
        s = append(s, 1)
        a := (*reflect.SliceHeader)(unsafe.Pointer(&s))
        fmt.Printf("%+v\n", a)
    }
    
    // output:
    &{Data:18193560 Len:0 Cap:0}
    &{Data:842350567624 Len:1 Cap:1}
    &{Data:18193560 Len:0 Cap:0}
    
    // -----------------------------------------------------
    
    // 通过传slice指针,实现对slice的修改
    func main() {
        s := make([]int, 0)
        a := (*reflect.SliceHeader)(unsafe.Pointer(&s))
        fmt.Printf("%+v\n", a)
    
        test(&s)
    
        b := (*reflect.SliceHeader)(unsafe.Pointer(&s))
        fmt.Printf("%+v\n", b)
    }
    
    func test(s *[]int) {
        *s = append(*s, 1)
        a := (*reflect.SliceHeader)(unsafe.Pointer(s))
        fmt.Printf("%+v\n", a)
    }
    
    // output:
    &{Data:18193560 Len:0 Cap:0}
    &{Data:842350567624 Len:1 Cap:1}
    &{Data:842350567624 Len:1 Cap:1}
    
  • json自定义序列化

    对于使用结构体中嵌套结构体的情况,只有receiver为指针类型,而嵌套结构体为结构体的值语义的时候不能触发自定义Json格式化函数MarshalJSON;其他三种组合均能够触发。

    对于使用结构体中嵌套结构体slice的情况,receiver值语义、指针语义和嵌套结构体slice元素为值语义、指针语义的四种组合均能够触发Json格式化函数MarshalJSON。

    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    type Profile struct {
        Level string
        Admin bool
    }
    
    // 本质是将Profile指针类型实现Marshaler接口,从而达到自定义json序列化格式的目的。
    func (p *Profile) MarshalJSON() ([]byte, error) {
        if p.Admin {
            admin := struct {
                Level string
            }{
                Level: "admin",
            }
            return json.Marshal(admin)
        }
    
        control := struct {
            Level string
            Admin bool
        }{
            Level: "control",
            Admin: false,
        }
        return json.Marshal(control)
    }
    
    type User struct {
        Id      int
        Name    string
        Age     uint8
        Profile *Profile
    }
    
    func main() {
        u := User{
            Id:   1,
            Age:  23,
            Name: "qshuai",
            Profile: &Profile{
                Level: "master",
                Admin: true,
            },
        }
        b, err := json.Marshal(u)
        if err != nil {
            panic(err)
        }
        fmt.Println(string(b))
    }
    
    // -----------------------------slice作为Struct成员的情况----------------------------
    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    type Profile struct {
        Level string
        Admin bool
    }
    
    func (p *Profile) MarshalJSON() ([]byte, error) {
        if p.Admin {
            admin := struct {
                Level string
            }{
                Level: "admin",
            }
            return json.Marshal(admin)
        }
    
        control := struct {
            Level string
            Admin bool
        }{
            Level: "control",
            Admin: false,
        }
        return json.Marshal(control)
    }
    
    type User struct {
        Id      int
        Name    string
        Age     uint8
        Profile []Profile
    }
    
    func main() {
        u := User{
            Id:   1,
            Age:  23,
            Name: "qshuai",
            Profile: []Profile{
                {
                    Level: "master",
                    Admin: true,
                },
            },
        }
        b, err := json.Marshal(u)
        if err != nil {
            panic(err)
        }
        fmt.Println(string(b))
    }
    
  • 使用map[string]interface{}接收不确定json数据

    data := []byte(`
    {
    "address": "16WtgAckGAYLHaxJnRFV5mueF8gaEbKc4W",
    "received": {"name":"qshuai", "age":23},
    "sent": ["abc", 124, "def"]
    }`)
    
    var d map[string]interface{}
    err := json.NewDecoder(bytes.NewReader(data)).Decode(&d)
    if err != nil {
        panic(err)
    }
    
    spew.Dump(d)
    
    // output
    (map[string]interface {}) (len=3) {
     (string) (len=7) "address": (string) (len=34) "16WtgAckGAYLHaxJnRFV5mueF8gaEbKc4W",
     (string) (len=8) "received": (map[string]interface {}) (len=2) {
      (string) (len=4) "name": (string) (len=6) "qshuai",
      (string) (len=3) "age": (float64) 23
     },
     (string) (len=4) "sent": ([]interface {}) (len=3 cap=4) {
      (string) (len=3) "abc",
      (float64) 124,
      (string) (len=3) "def"
     }
    }
    
  • 不可寻址的情况

    • 常量

    • map的值且值为值类型

    • 函数返回值并且返回值为值类型

    • 接口断言且断言成值类型成功

    • 函数返回值为数组类型(非数组指针类型),对返回值直接做[m:n]取slice的操作是不合法的

  • 赋值简写方式的坑

    // 1、同一作用域 := 左侧至少有一个未申明
    // 2、同一作用域 := 左侧重复的变量名的变量地址是相同的
    package main
    
    import "fmt"
    
    func main() {
        a, b := 1, 2
        fmt.Printf("%p\n", &a)
    
        a, c := 2, 3
    
        fmt.Printf("%p\n", &a)
        fmt.Println(b, c)
    }
    
  • for…range 坑

    package main
    
    import "fmt"
    
    type T struct {
        ID int
    }
    
    func (t *T) PrintID() {
        fmt.Println(t.ID)
    }
    
    func F1() {
        ts := []T{{1}, {2}, {3}}
        for _, t := range ts {  // 每次遍历,将ts中的相应的元素拷贝到临时变量t,同一个迭代的t的内存地址是相同的
            defer t.PrintID()           // 先取出t的地址,然后压栈
        }
    }
    
    func F2() {
        ts := []*T{&T{1}, &T{2}, &T{3}}
        for _, t := range ts {
            defer t.PrintID()
        }
    }
    
    func main() {
        fmt.Println("F1()")
        F1()
        fmt.Println()
        fmt.Println("F2()")
        F2()
    }
    
  • == nil 的判断结果: 如果左侧为接口类型,那么只有接口变量的值和类型均为nil的情况下,== nil 才能成立;如果左侧是普通数据类型,那么只要其变量的值为nil,那么== nil就会成立。

  • Example_开头的函数会在go test时自动运行。

  • golang中的引用类型: slice、map、channel、function、interface、pointer

  • nil指针可以调用自己的方法,但是不能在函数内对指针进行解引用或者访问其成员变量

    package main
    
    import "fmt"
    
    type user struct {
        ID   int
        Name string
    }
    
    func (u *user) getInfo() {
        fmt.Println("hello world")
        // fmt.Println(u.Name)      // will panic
    }
    func main() {
        var u *user
    
        if u == nil {
            fmt.Println("u == nil")
        }
        u.getInfo()     // ok
    }
    
  • nil slice除了不能使用索引访问外,其他操作都是可以的

    package main
    
    import "fmt"
    
    func main() {
        var s []int
        s[0] = 1                        // panic
        fmt.Println(s[1])               // panic
        fmt.Println(len(s), cap(s))     // 0 0
    
        for index, item := range s {    // nothing
            fmt.Println(index, item)
        }
    
        s = append(s, 1)
        fmt.Println(s)                  // [1]
    }
    
  • nil map除了不能写入元素外,其他操作都是允许的(可以把nil map看成只读的map)

    package main
    
    import "fmt"
    
    func main() {
        var m map[int]string
    
        fmt.Println(m == nil)           // true
    
        fmt.Println(len(m))             // 0
    
        for key, value := range m {     // nothing
            fmt.Println(key, value)
        }
    
        item, ok := m[2]
        fmt.Println(item, ok)           //  false
    
        m[0] = "qshuai"                 // panic
    
        m1 := map[int]string{}
        fmt.Println(m1 == nil)          // false
        
        m1[1] = "qshuai"
        fmt.Println(m1[1])              // qshuai
    }
    
  • nil channel的写入和读取都会造成死锁,而关闭一个nil channel会引起panic

    func main() {
        var c chan int
    
        fmt.Println(c == nil) // true
        <-c                   // deadlock
        c <- 3                // deadlock
        close(c)              // panic
    }
    

    已关闭的channle仍然可以被读取,只不过结果总为对应类型的零值

    func main() {
        cc := make(chan int)
    
        go func() {
        stop:
            for {
                select {
                case v, ok := <-cc:
                    if ok {
                        fmt.Println(v)
                    } else {
                        break stop // will not into for loop
                    }
    
                }
            }
        }()
    
        go func() {
            var i int
            for {
                cc <- i
                i++
                time.Sleep(500 * time.Millisecond)
    
                if i > 5 {
                    close(cc)
                    break
                }
            }
        }()
    
        time.Sleep(10 * time.Second)
    }
    
  • switch中fallthrough用法: fallthrough将执行下一个case内的语句, 而不用判断case的条件是否满足。

    func main() {
        i := 3
    
        switch i {
        default:
            fallthrough
        case 1:
            fmt.Println("ok")
        case 2:
            fmt.Println("false")
        }
    }
    
    // output: ok
    
  • switch case的高级用法

    func isAcceptableKind(kind reflect.Kind) bool {
        switch kind {
        case reflect.Chan:
            fallthrough
        case reflect.Complex64:
            fallthrough
        case reflect.Complex128:
            fallthrough
        case reflect.Func:
            fallthrough
        case reflect.Ptr:
            fallthrough
        case reflect.Interface:
            return false
        }
    
        return true
    }
    
  • slice用法

    data[:6:8] 每个数字前都有个冒号, slice内容为data从0到第6位,长度len为6,最大扩充项cap设置为8 
    
  • 检查关闭通道

    w.quitMu.Lock()
    // select 中只有一个通道操作,之所有用select是因为,select可以通过default的方式避开
    // 堵塞的情况
    select {
    case <-w.quit:
      w.quitMu.Unlock()
      return
    default:
    }
    

推荐阅读更多精彩内容

  • fmt格式化字符串 格式:%[旗标][宽度][.精度][arg索引]动词旗标有以下几种:+: 对于数值类型总是输出...
    皮皮v阅读 360评论 0 3
  • 选择题 [primary] 下面属于关键字的是() A. func B. def C. struct D. cla...
    盘木阅读 763评论 0 28
  • Notes Section 2, Program Structure nested block in if-els...
    keysaim阅读 152评论 0 1
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 6,697评论 0 39
  • 美术学习,帮助孩子拥有一双发现美的眼睛。美术学习不是为了将某一个事物永绘画的方式表达得具象,而是孩子发现美、感受美...
    色彩系阅读 303评论 0 0