
通過兩個例子介紹一下 For Range 內(nèi)部實現(xiàn)原理
下面的代碼是死循環(huán)么?
func main() {
v := []int{1, 2, 3}
for i := range v {
v = append(v, i)
}
}
上面的代碼先初始化了一個內(nèi)容為1、2、3的slice,然后遍歷這個slice,然后給這個切片追加元素。隨著遍歷的進行,數(shù)組v也在逐漸增大,那么這個for循環(huán)是一個死循環(huán)么?
答案是否。只會遍歷三次,v的結(jié)果是[0, 1, 2]。并不是死循環(huán),原因就在于for range實現(xiàn)的時候用到了語法糖。
語法糖
語法糖(Syntactic sugar),也譯為糖衣語法,是由英國計算機科學家彼得·蘭丁發(fā)明的一個術(shù)語,指計算機語言中添加的某種語法,這種語法對語言的功能沒有影響,但是更方便程序員使用。 語法糖讓程序更加簡潔,有更高的可讀性。
對于切片的for range,它的底層代碼就是:
// 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
// }
可以看到,在遍歷之前就獲取的切片的長度len_temp := len(for_temp),遍歷的次數(shù)不會隨著切片的變化而變化,上面的代碼自然不會是死循環(huán)了。
下面的代碼有什么問題么?
slice := []int{0, 1, 2, 3}
myMap := make(map[int]*int)
for index, value := range slice {
fmt.Println(&index, &value)
myMap[index] = &value
}
fmt.Println("=====new map=====")
for k, v := range myMap {
fmt.Printf("%d => %d\n", k, *v)
}
這也是實際編碼中有可能會遇到的問題,循環(huán)切片,index和value地址一開始分配好后,后面還是那個地址,把切片值的地址保存到myMap中,這樣的操作結(jié)果是:
=====new map=====
0 => 3
1 => 3
2 => 3
3 => 3
結(jié)果完全一樣,都是最后一次遍歷的值。通過上面的底層代碼看下,遍歷后的值賦給了value,而在我們的例子中,會把value的地址保存到myMap的值中。這里的value是個「全局變量」,所以賦完值之后myMap里面所有的值都是value,所以結(jié)構(gòu)都是一樣的而且是最后一個值。
注意,這里必須是保存指針才會有問題,如果直接保存的是value,因為 Golang 是值拷貝,所以值會重新復制再保存,這種情況下結(jié)果就會是正確的了。
切片F(xiàn)or Range原理
總結(jié)一下,通過For Range遍歷切片,首先,計算遍歷次數(shù)(切片長度);每次遍歷,都會把當前遍歷到的值存放到一個全局變量index中。
其它語法糖
另外,F(xiàn)or Range 不光支持切片。其它的語法糖底層代碼。
map
// Lower a for range over a map.
// The loop we generate:
// var hiter map_iteration_struct
// for mapiterinit(type, range, &hiter); hiter.key != nil; mapiternext(&hiter) {
// index_temp = *hiter.key
// value_temp = *hiter.val
// index = index_temp
// value = value_temp
// original body
// }
channel
// Lower a for range over a channel.
// The loop we generate:
// for {
// index_temp, ok_temp = <-range
// if !ok_temp {
// break
// }
// index = index_temp
// original body
// }
數(shù)組
// Lower a for range over an array.
// 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
// }
字符串
// Lower a for range over a string.
// The loop we generate:
// len_temp := len(range)
// var next_index_temp int
// for index_temp = 0; index_temp < len_temp; index_temp = next_index_temp {
// value_temp = rune(range[index_temp])
// if value_temp < utf8.RuneSelf {
// next_index_temp = index_temp + 1
// } else {
// value_temp, next_index_temp = decoderune(range, index_temp)
// }
// index = index_temp
// value = value_temp
// // original body
// }