【面試篇】Go語言常見踩坑(一)

NoSay發表於2021-10-29

引言

本系列會列舉一些在Go面試中常見的問題。

切片迴圈問題

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: <nil>,為什麼呢?因為在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())
        ......
}

關注我們

歡迎對本系列文章感興趣的讀者訂閱我們的公眾號,關注博主下次不迷路~
image.png

相關文章