引言
本系列會列舉一些在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())
......
}
關注我們
歡迎對本系列文章感興趣的讀者訂閱我們的公眾號,關注博主下次不迷路~