Go 語言常見踩坑記
引言
本系列會列舉一些在Go面試中常見的問題。
切片循環(huán)問題
For循環(huán)在我們日常編碼中可能用的很多。在很多業(yè)務場景中我們都需要用for循環(huán)處理。但golang中的for循環(huán)在使用上需要注意一些問題,大家可否遇到。先看下邊這一段代碼:
- 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循環(huán)中,循環(huán)內部創(chuàng)建的函數變量都是共享同一塊內存地址,for循環(huán)總是使用同一塊內存去接收循環(huán)中的的value變量的值。不管循環(huán)多少次,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
符合預期。如果大家比較感興趣的話可以去將這段代碼的匯編打印出來,就可以發(fā)現(xiàn)循環(huán)的v一直在操作同一塊內存。
同樣的,在slice循環(huán)這塊我們還會遇見另一個有趣的地方,大家可以看看下邊這段代碼輸出什么?
- 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在循環(huán)前會先拷貝一個a,然后對新拷貝的a進行操作,所以循環(huán)的次數不會隨著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:
可變參數是空接口類型
當參數的可變參數是空接口類型時,傳入空接口的切片時需要注意參數展開的問題。
- 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,就會發(fā)現(xiàn)下邊這個代碼用來決定從哪開始遍歷map。另一個原因是map 在某些特定情況下(例如擴容),會發(fā)生key的搬遷重組。而遍歷的過程,就是按順序遍歷bucket,同時按順序遍歷bucket中的key。搬遷后,key的位置發(fā)生了重大的變化,所以遍歷map的結果就不可能按原來的順序了。
- func mapiterinit(t *maptype, h *hmap, it *hiter) {
- ......
- // decide where to start
- r := uintptr(fastrand())
- ......
- }