原文鏈接: 為什麼 Go for-range 的 value 值地址每次都一樣? 迴圈語句是一種常用的控制結構,在 Go 語言中,除了 for 關鍵字以外,還有一個 range 關鍵字,可以使用 for-range 迴圈迭代數組、切片、字元串、map 和 channel 這些數據類型。 但是在使用 ...
原文鏈接: 為什麼 Go for-range 的 value 值地址每次都一樣?
迴圈語句是一種常用的控制結構,在 Go 語言中,除了 for
關鍵字以外,還有一個 range
關鍵字,可以使用 for-range
迴圈迭代數組、切片、字元串、map 和 channel 這些數據類型。
但是在使用 for-range
迴圈迭代數組和切片的時候,是很容易出錯的,甚至很多老司機一不小心都會在這裡翻車。
具體是怎麼翻的呢?我們接著看。
現象
先來看兩段很有意思的代碼:
無限迴圈
如果我們在遍曆數組的同時向數組中添加元素,能否得到一個永遠都不會停止的迴圈呢?
比如下麵這段代碼:
func main() {
arr := []int{1, 2, 3}
for _, v := range arr {
arr = append(arr, v)
}
fmt.Println(arr)
}
程式輸出:
$ go run main.go
1 2 3 1 2 3
上述代碼的輸出意味著迴圈只遍歷了原始切片中的三個元素,我們在遍歷切片時追加的元素並沒有增加迴圈的執行次數,所以迴圈最終還是停了下來。
相同地址
第二個例子是使用 Go 語言經常會犯的一個錯誤。
當我們在遍歷一個數組時,如果獲取 range
返回變數的地址並保存到另一個數組或者哈希時,會遇到令人困惑的現象:
func main() {
arr := []int{1, 2, 3}
newArr := []*int{}
for _, v := range arr {
newArr = append(newArr, &v)
}
for _, v := range newArr {
fmt.Println(*v)
}
}
程式輸出:
$ go run main.go
3 3 3
上述代碼並沒有輸出 1 2 3
,而是輸出 3 3 3
。
正確的做法應該是使用 &arr[i]
替代 &v
,像這種編程中的細節是很容易出錯的。
原因
具體原因也並不複雜,一句話就能解釋。
對於數組、切片或字元串,每次迭代,for-range
語句都會將原始值的副本傳遞給迭代變數,而非原始值本身。
口說無憑,具體是不是這樣,還得靠源碼說話。
Go 編譯器會將 for-range
語句轉換成類似 C 語言的三段式迴圈結構,就像這樣:
// Arrange to do a loop appropriate for the type. We will produce
// for INIT ; COND ; POST {
// ITER_INIT
// INDEX = INDEX_TEMP
// VALUE = VALUE_TEMP // If there is a value
// original statements
// }
迭代數組時,是這樣:
// The loop we generate:
// len_temp := len(range)
// range_temp := range
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = range_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }
切片:
// for_temp := range
// len_temp := len(for_temp)
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = for_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }
從上面的代碼片段,可以總結兩點:
- 在迴圈開始前,會將數組或切片賦值給一個新變數,在賦值過程中就發生了拷貝,迭代的實際上是副本,這也就解釋了現象 1。
- 在迴圈過程中,會將迭代元素賦值給一個臨時變數,這又發生了拷貝。如果取地址的話,每次都是一樣的,都是臨時變數的地址。
以上就是本文的全部內容,如果覺得還不錯的話歡迎點贊,轉發和關註,感謝支持。
參考文章:
- https://garbagecollected.org/2017/02/22/go-range-loop-internals/
- https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-for-range/
推薦閱讀: