【Golang】小知識總結(jié), 你中招了沒有

  • 使用定時器

    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轉(zhuǎn)array

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

    原理解析:arr[:]是一個引用了底層數(shù)組arr的slice,長度和容量與數(shù)組的長度相同,也就是說這兩個元素共用一塊內(nèi)存。所以當copy()時,改變了slice中的元素值,而數(shù)組的改變可以說是一種副效應(side-effect)。

  • 通過append函數(shù)刪除slice中的元素,是否會重新分配內(nèi)存的問題

    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的數(shù)據(jù)也變化了,那么說明通過append()
      // 刪除slice中的數(shù)據(jù)并沒有重新分配內(nèi)存空間
      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)
    }
    
    // 規(guī)律是 i ^ i = 0
    
  • 如果當前函數(shù)或者其調(diào)用棧上游,有處理panic的recover(),panic()就不會輸出,更不會終止程序,但是panic()的確執(zhí)行了。

    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轉(zhuǎn)十六進制

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

    給定一個十六進制數(shù)0xOA0B0C0D,以下給出了在兩種不同方法下內(nèi)存的排序方式,內(nèi)存地址從左向右依次增大。上方為小端法表示、下方為大端法表示。

    概念:

    • 大端法: 數(shù)字的高位和低位與內(nèi)存地址相反
    • 小端法: 數(shù)字的高位和低位與內(nèi)存地址相同

    比較好記的是大端法數(shù)字的書寫順序與內(nèi)存排列順序一致

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

    在golang中將一個數(shù)字轉(zhuǎn)換成byte,在小端法中,會解析低內(nèi)存地址的一個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)
    }
    
  • 標識符沖突

    同一個包內(nèi)不能出現(xiàn)變量名、常量名、函數(shù)名兩兩沖突的情況,如下情況編譯失敗:

    package main
    
    import "fmt"
    
    const conflict = 38
    
    var conflict = 34
    
    func conflict() {
        fmt.Println(conflict)
    }
    
    func main() {
        fmt.Println(conflict)
        conflict()
    }
    
  • 求兩個時間的間隔,會參數(shù)負數(shù)的情況

    func main() {
        t1 := time.Now()
        t2 := time.Now()
        fmt.Println(t1.Sub(t2)) // 負數(shù)
      
        t3 := time.Unix(math.MaxInt64, 0) // 屬于越界的情況
        fmt.Println(t3.Sub(t1))
    }
     
    // output
    -657ns
    -2562047h47m16.854775808s
    

    所以在使用time包的Sub()函數(shù)時,要將結(jié)果與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循環(huán)控制及返回值

        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默認是堵塞的,必須有一個分支執(zhí)行才能出去。

        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")
            }
        }
             
        // 如果所有通道中都沒有內(nèi)容,可以使用default語句,繼續(xù)程序的執(zhí)行。不過要看業(yè)務需求。
        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結(jié)構(gòu)中default語句的執(zhí)行與其所在位置無關

    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 結(jié)構(gòu)); 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()函數(shù)在channel的應用。當chanel為無緩沖的時候,len()在任何情況下都會返回0; 當channel為有緩沖的情況下,len()會返回當前緩沖中的元素個數(shù),增大或減小取決于生產(chǎn)者和消費者的速度??傊?,len()函數(shù)在channel中返回的是緩沖區(qū)的元素個數(shù)。

    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語句實現(xiàn)代碼跳轉(zhuǎn)

    // 既可以往前跳,也可以往后跳
    func main() {
    previous:
        fmt.Println("previous")
        i := 0
        if i == 0 {
            goto next
        }
    next:
        fmt.Println("next")
        goto previous
    }
    
    // break跳轉(zhuǎn)至指定標簽后,將不執(zhí)行標簽對應的for循環(huán)。需要注意的是:標簽只能定義在for循環(huán)之前。
    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跳轉(zhuǎn)到指定標簽后,如果for循環(huán)條件滿足仍然會執(zhí)行for循環(huán),直到條件不滿足為止。需要注意的是:標簽只能定義在for循環(huán)之前。
    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被觸發(fā)的條件:

    1、通道中有數(shù)據(jù)

    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
    }
    
  • 函數(shù)內(nèi)對slice作append,在函數(shù)外不可見: 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指針,實現(xiàn)對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自定義序列化

    對于使用結(jié)構(gòu)體中嵌套結(jié)構(gòu)體的情況,只有receiver為指針類型,而嵌套結(jié)構(gòu)體為結(jié)構(gòu)體的值語義的時候不能觸發(fā)自定義Json格式化函數(shù)MarshalJSON;其他三種組合均能夠觸發(fā)。

    對于使用結(jié)構(gòu)體中嵌套結(jié)構(gòu)體slice的情況,receiver值語義、指針語義和嵌套結(jié)構(gòu)體slice元素為值語義、指針語義的四種組合均能夠觸發(fā)Json格式化函數(shù)MarshalJSON。

    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    type Profile struct {
        Level string
        Admin bool
    }
    
    // 本質(zhì)是將Profile指針類型實現(xiàn)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數(shù)據(jù)

    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的值且值為值類型

    • 函數(shù)返回值并且返回值為值類型

    • 接口斷言且斷言成值類型成功

    • 函數(shù)返回值為數(shù)組類型(非數(shù)組指針類型),對返回值直接做[m:n]取slice的操作是不合法的

  • 賦值簡寫方式的坑

    // 1、同一作用域 := 左側(cè)至少有一個未申明
    // 2、同一作用域 := 左側(cè)重復的變量名的變量地址是相同的
    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的內(nèi)存地址是相同的
            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 的判斷結(jié)果: 如果左側(cè)為接口類型,那么只有接口變量的值和類型均為nil的情況下,== nil 才能成立;如果左側(cè)是普通數(shù)據(jù)類型,那么只要其變量的值為nil,那么== nil就會成立。

  • Example_開頭的函數(shù)會在go test時自動運行。

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

  • nil指針可以調(diào)用自己的方法,但是不能在函數(shù)內(nèi)對指針進行解引用或者訪問其成員變量

    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仍然可以被讀取,只不過結(jié)果總為對應類型的零值

    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將執(zhí)行下一個case內(nèi)的語句, 而不用判斷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] 每個數(shù)字前都有個冒號, slice內(nèi)容為data從0到第6位,長度len為6,最大擴充項cap設置為8 
    
  • 檢查關閉通道

    w.quitMu.Lock()
    // select 中只有一個通道操作,之所有用select是因為,select可以通過default的方式避開
    // 堵塞的情況
    select {
    case <-w.quit:
      w.quitMu.Unlock()
      return
    default:
    }
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容