通過兩個例子介紹一下 Golang For Range 循環(huán)原理(轉(zhuǎn))

原文

IMG-THUMBNAIL

通過兩個例子介紹一下 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
//   }

完整底層代碼。


推薦閱讀:Go Range Loop Internals

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容