Go 語(yǔ)言常見(jiàn)踩坑記
引言
本系列會(huì)列舉一些在Go面試中常見(jiàn)的問(wèn)題。
切片循環(huán)問(wèn)題
For循環(huán)在我們?nèi)粘>幋a中可能用的很多。在很多業(yè)務(wù)場(chǎng)景中我們都需要用for循環(huán)處理。但golang中的for循環(huán)在使用上需要注意一些問(wè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
那么為什么會(huì)輸出的是這個(gè)結(jié)果呢?
在golang的for循環(huán)中,循環(huán)內(nèi)部創(chuàng)建的函數(shù)變量都是共享同一塊內(nèi)存地址,for循環(huán)總是使用同一塊內(nèi)存去接收循環(huán)中的的value變量的值。不管循環(huán)多少次,value的內(nèi)存地址都是相同的。我們可以測(cè)試一下:
- func testSliceWithAddress() {
- a := []int64{1,2,3}
- for _, v := range a {
- go func() {
- fmt.Println(&v)
- }()
- }
- time.Sleep(time.Second)
- }
- output:
- 0xc0000b2008
- 0xc0000b2008
- 0xc0000b2008
符合預(yù)期。如果大家比較感興趣的話可以去將這段代碼的匯編打印出來(lái),就可以發(fā)現(xiàn)循環(huán)的v一直在操作同一塊內(nèi)存。
同樣的,在slice循環(huán)這塊我們還會(huì)遇見(jiàn)另一個(gè)有趣的地方,大家可以看看下邊這段代碼輸出什么?
- func testRange3() {
- a := []int64{1,2,3}
- for _, v := range a {
- a = append(a, v)
- }
- fmt.Println(a)
- }
這段代碼的輸出結(jié)果是:[1 2 3 1 2 3],為什么呢?因?yàn)間olang在循環(huán)前會(huì)先拷貝一個(gè)a,然后對(duì)新拷貝的a進(jìn)行操作,所以循環(huán)的次數(shù)不會(huì)隨著append而增多。
interface和nil比較
比如返回了一個(gè)空指針,但并不是一個(gè)空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)
- }
- }
輸出結(jié)果為:result:
可變參數(shù)是空接口類(lèi)型
當(dāng)參數(shù)的可變參數(shù)是空接口類(lèi)型時(shí),傳入空接口的切片時(shí)需要注意參數(shù)展開(kāi)的問(wèn)題。
- func testVolatile() {
- var a = []interface{}{1, 2, 3}
- fmt.Println(a)
- fmt.Println(a...)
- }
輸出結(jié)果為:
- [1 2 3]
- 1 2 3
map遍歷時(shí)順序不固定
不要相信map的順序!
- func testMap() {
- m := map[string]string{
- "a": "a",
- "b": "b",
- "c": "c",
- }
- for k, v := range m {
- println(k, v)
- }
- }
具體原因大家可以看一下源碼:map.go:mapiterinit,就會(huì)發(fā)現(xiàn)下邊這個(gè)代碼用來(lái)決定從哪開(kāi)始遍歷map。另一個(gè)原因是map 在某些特定情況下(例如擴(kuò)容),會(huì)發(fā)生key的搬遷重組。而遍歷的過(guò)程,就是按順序遍歷bucket,同時(shí)按順序遍歷bucket中的key。搬遷后,key的位置發(fā)生了重大的變化,所以遍歷map的結(jié)果就不可能按原來(lái)的順序了。
- func mapiterinit(t *maptype, h *hmap, it *hiter) {
- ......
- // decide where to start
- r := uintptr(fastrand())
- ......
- }


































