簡單介紹Go 語言常見的一些坑

roc_guo發表於2021-11-07
切片迴圈問題

For迴圈在我們日常編碼中可能用的很多。在很多業務場景中我們都需要用for迴圈處理。但golang中的for迴圈在使用上需要注意一些問題,大家可否遇到。先看下邊這一段程式碼:

func testSlice() { 
    a := []int64{1,2,3} 
    for _, v := range a { 
        go func() { 
            fmt.Println(v) 
        }() 
    } 
     
    time.Sleep(time.Second) 
} 
 
output: 3 3 3

那麼為什麼會輸出的是這個結果呢?

在golang的for迴圈中,迴圈內部建立的函式變數都是共享同一塊記憶體地址,for迴圈總是使用同一塊記憶體去接收迴圈中的的value變數的值。不管迴圈多少次,value的記憶體地址都是相同的。我們可以測試一下:

func testSliceWithAddress() { 
    a := []int64{1,2,3} 
    for _, v := range a { 
        go func() { 
            fmt.Println(&v) 
        }() 
 
    } 
 
    time.Sleep(time.Second) 
} 
 
output: 
        0xc0000b2008 
        0xc0000b2008 
        0xc0000b2008

符合預期。如果大家比較感興趣的話可以去將這段程式碼的彙編列印出來,就可以發現迴圈的v一直在操作同一塊記憶體。

同樣的,在slice迴圈這塊我們還會遇見另一個有趣的地方,大家可以看看下邊這段程式碼輸出什麼?

func testRange3() { 
    a := []int64{1,2,3} 
    for _, v := range a { 
        a = append(a, v) 
    } 
 
    fmt.Println(a) 
}

這段程式碼的輸出結果是:[1 2 3 1 2 3],為什麼呢?因為golang在迴圈前會先複製一個a,然後對新複製的a進行操作,所以迴圈的次數不會隨著append而增多。

interface和nil比較

比如返回了一個空指標,但並不是一個空interface

func testInterface() { 
    doit := func(arg int) interface{} { 
        var result * struct{} = nil 
        if arg > 0 { 
            result = &struct{}{} 
        } 
 
        return result 
    } 
 
    if res := doit(-1); res != nil { 
        fmt.Println("result:", res) 
    } 
}

輸出結果為:result: ,為什麼呢?因為在go裡邊變數有型別和值兩個屬性,在比較的時候也會比較型別和值都相同才會認為相等。程式碼中result的型別是指標,值是nil,所以會有這樣的輸出。

可變引數是空介面型別

當引數的可變引數是空介面型別時,傳入空介面的切片時需要注意引數展開的問題。

func testVolatile() { 
    var a = []interface{}{1, 2, 3} 
 
    fmt.Println(a) 
    fmt.Println(a...) 
} 
輸出結果為:
[1 2 3] 
1 2 3

map遍歷時順序不固定

不要相信map的順序!

func testMap() { 
    m := map[string]string{ 
        "a": "a", 
        "b": "b", 
        "c": "c", 
    } 
 
    for k, v := range m { 
        println(k, v) 
    } 
}

具體原因大家可以看一下原始碼:map.go:mapiterinit,就會發現下邊這個程式碼用來決定從哪開始遍歷map。另一個原因是map 在某些特定情況下(例如擴容),會發生key的搬遷重組。而遍歷的過程,就是按順序遍歷bucket,同時按順序遍歷bucket中的key。搬遷後,key的位置發生了重大的變化,所以遍歷map的結果就不可能按原來的順序了。

func mapiterinit(t *maptype, h *hmap, it *hiter) { 
        ...... 
    // decide where to start 
    r := uintptr(fastrand()) 
        ...... 
}


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901823/viewspace-2840988/,如需轉載,請註明出處,否則將追究法律責任。

相關文章