go只提供了一種迴圈方式,即for
迴圈,在使用時可以像c那樣使用,也可以通過for range
方式遍曆容器類型如數組、切片和映射。但是在使用for range
時,如果使用不當,就會出現一些問題,導致程式運行行為不如預期。
情形 . 一
> for range + 閉包
代碼如下:
package mainimport ( "fmt" "time")func main() { str := []string{"I","am","Ethan"} for _,v := range str{ go func() { fmt.Println(v) }() } time.Sleep(3 * time.Second)}----------輸出結果:EthanEthanEthan
程式設計的原意是遍曆字串切片並將其列印出來,可是輸出結果卻只輸出了切片的最後一個元素.
原因是 : 閉包裡引用了不作為參數傳遞進去的值,都是引用傳遞...也就是說,println(v)
其實是引用了v的地址然後解引用,將值列印出來..等到這個goroutine執行println(v)
的時候,v所指向的值已經是"Ethan"
如果要正確列印,在定義閉包的時候要定義一個參數,將v作為參數傳遞進去.修改後代碼如下:
package mainimport ( "fmt" "time")func main() { str := []string{"I","am","Ethan"} for _,v := range str{ go func(v string) { fmt.Println(v) }(v) } time.Sleep(3 * time.Second)}
此外,輸出結果不一定是 i am Sergey,因為goroutine執行順序有go runtime調度器決定
情形 . 二
> 操作map,原理和情形一類似
代碼如下:
package mainimport "fmt"func main() { slice := []int{0, 1, 2, 3} myMap := make(map[int]*int) for index , value := range slice { myMap[index] = &value } prtMap(myMap)}func prtMap(myMap map[int]*int) { for key, value := range myMap { fmt.Printf("map[%v]=%v\n", key, *value) }}----------輸出結果:map[0]=3map[1]=3map[2]=3map[3]=3
原因解釋:但是由輸出可以知道,映射的值都相同且都是3。其實可以猜測映射的值都是同一個地址,遍曆到切片的最後一個元素3時,將3寫入了該地址,所以導致映射所有值都相同。
其實真實原因也是如此,因為for range建立了每個元素的副本,而不是直接返回每個元素的引用,如果使用該值變數的地址作為指向每個元素的指標,就會導致錯誤,在迭代時,返回的變數是一個迭代過程中根據切片依次賦值的新變數,所以值的地址總是相同的,導致結果不如預期。
做一下小小的修改就能得到我們的預期結果:
package mainimport "fmt"func main() { slice := []int{0, 1, 2, 3} myMap := make(map[int]*int) for index , value := range slice { v := value myMap[index] = &v } prtMap(myMap)}func prtMap(myMap map[int]*int) { for key, value := range myMap { fmt.Printf("map[%v]=%v\n", key, *value) }}