原文轉載 Go 陷阱之 for 迴圈迭代變數
捕獲迭代變數
這是在學習 Go 程式設計 中遇到的一個比較重要的一個警告。這是個 Go 語言的詞法作用域規則的陷阱。看完之後感覺是真的一個比較讓人疑惑困惑的地方。所以特地記錄一下。由標題就可以知道了,迭代變數,肯定是在 for
中遇到的問題。來看一個簡單的例子說明一下這個問題所在。
看一段簡單的程式碼, 首先是錯誤的示例:
var slice []func()
func main() {
sli := []int{1, 2, 3, 4, 5}
for _, v := range sli {
fmt.Println(&v)
slice = append(slice, func(){
fmt.Println(v * v) // 直接列印結果
})
}
for _, val := range slice {
val()
}
}
// 輸出 25 25 25 25 25
你可能會很奇怪為什麼會出現這種情況, 結果不應該是 1, 4, 9, 16, 25
嗎?其實原因是迴圈變數的作用域的規則限制。在上面的程式中,v
在 for 迴圈引進的一個塊作用域內進行宣告。在迴圈裡建立的所有函式變數共享相同的變數,就是一個可訪問的儲存位置,而不是固定的值
。(你會驚奇的發現 &v
的記憶體地址是一樣的)
模擬一下實際的情況,假設 v 變數的地址在 0x12345678 上, for 迴圈在迭代過程中,所有變數值都是在這地址上迭代的。當最後呼叫匿名函式的時候,取值也是在這塊地址上。所以最後輸出的結果都是迭代的最後一個值。至少在 Go 語言中是不用質疑的。這裡也是一個陷阱,如果你不清楚的話,肯定會遇到坑。那個該如何修改呢?
var slice []func()
func main() {
sli := []int{1, 2, 3, 4, 5}
for _, v := range sli {
temp := v // 其實很簡單 引入一個臨時區域性變數就可以了,這樣就可以將每次的值儲存到該變數地址上
fmt.Println(&temp) // 這裡記憶體地址是不同的
slice = append(slice, func(){
fmt.Println(temp * temp) // 直接列印結果
})
}
for _, val := range slice {
val()
}
}
// 輸出 1, 4, 9, 16, 25 預期結果
只需要引入一個區域性變數便可以解決了,這是必須的。否則你的程式將不會有可預期的結果。
本作品採用《CC 協議》,轉載必須註明作者和本文連結