Golang for range的坑

Time-Traveler發表於2020-12-25

本篇參考自:https://blog.csdn.net/somanlee/article/details/107881231,尊重原創


廢話就不說了,先來一段程式碼:
package main

import (
	"fmt"
)

func main() {

	slice := []int{0, 1, 2, 3}
	mp := make(map[int]*int)
	for index, value := range slice {
		mp[index] = &value
	}
	fmt.Println("-------------------------------------------------------------------")
	for key, value := range mp {
		fmt.Println(key, " ", *value)
	}
}

先猜一下輸出的結果是什麼,再看看結果跟你想的是不是一樣的
在這裡插入圖片描述
沒錯,全都是 333333,就差個2了

其根本原因就是 for range是用一個變數承接rang 後面的內容,也就是上面的mp,slice,什麼意思???
我們先看看為什麼全都是3,稍微做出一點改變:

func main() {

	slice := []int{0, 1, 2, 3}
	mp := make(map[int]*int)
	for index, value := range slice {
		mp[index] = &value
		fmt.Println("address is:",&value)
		fmt.Println("value is:",value)
	}
	//根本原因在於for range是用一個變數承接mp中的內容的
	fmt.Println("-------------------------------------------------------------------")
	for key, value := range mp {
		fmt.Println(key, " ", *value)
	}
}

結果如下:
在這裡插入圖片描述
有沒有發現,為什麼地址都是一樣的,而最後這個地址的值是3,mp中存的是又是這個地址,所以不全都是3了嘛。那又為什麼會是這樣了呢?上文中的承接是什麼意思呢??

for range

for index, value := range t_slice {
	original body
}

相當於:

len_temp := len(t_slice)
range_temp := t_slice
for index_temp = 0; index_temp < len_temp; index_temp++ {
     value_temp := range_temp[index_temp]
     index := index_temp
     value := value_temp
     original body
}

這個承接的變數就是 range_temp。

正確的寫法:

package main

import (
	"fmt"
)

func main() {

	slice := []int{0, 1, 2, 3}
	mp := make(map[int]*int)
	for index, value := range slice {
		num := value
		mp[index] = &num
		// mp[index] = &slice[index] 用切片的index也行
	}
	//根本原因在於for range是用一個變數承接mp中的內容的
	fmt.Println("-------------------------------------------------------------------")
	for key, value := range mp {
		fmt.Println(key, " ", *value)
	}
}

在for range中啟動攜程:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var m = []int{1, 2, 3}
	var wg sync.WaitGroup
	for i := range m {
		wg.Add(1)
 		go func() {
			fmt.Print(i)
			wg.Done()
    	}()
	}
	wg.Wait()
}

輸出結果為:222
上面的程式碼中,每輪迴圈中使用匿名函式起協程,匿名函式直接引用外部變數i,實際上,這就是golang中的閉包,閉包是匿名函式與匿名函式所引用環境的組合,匿名函式有動態建立的特性,這使得匿名函式不用通過引數傳遞的方式,就可以直接引用外部的變數。
每輪迴圈啟動一個協程,而協程啟動與迴圈變數遞增不是在同一個協程,協程啟動的速度遠小於迴圈執行的速度,所以即使是第一個協程剛起啟動時,迴圈變數可能已經遞增完畢。由於所有的協程共享迴圈變數i,而且這個i會在最後一個使用它的協程結束後被銷燬,所以最後的輸出結果都是迴圈變數的末值即2。

新增延時,確保協程啟動:

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var wg sync.WaitGroup
	var m = []int{1, 2, 3}
	for i := range m {
		wg.Add(1)
		go func() {
			fmt.Print(i)
			wg.Done()
		}()
		time.Sleep(time.Second)
	}
	wg.Wait()

}

傳入引數:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var m = []int{1, 2, 3}
	var wg sync.WaitGroup
	for i := range m {
		wg.Add(1)
 		go func(i int) {
			fmt.Print(i)
			wg.Done()
    	}(i)
	}
	wg.Wait()

}

區域性變數:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var m = []int{1, 2, 3}
	var wg sync.WaitGroup
	for i := range m {
		wg.Add(1)
		n := i
		go func() {
			fmt.Print(n)
			wg.Done()
		}()
	}
	wg.Wait()
}

每次for的時候n都是不同的變數

相關文章