新聞中心
前言
用過golang的同學(xué),相信對「for range」是再熟悉不過了,可以說在任何語言中,循環(huán)遍歷都是常用的再也不能常用的一種方式,不過最近發(fā)現(xiàn)了一個問題,其實挺坑的,今天總結(jié)一下,希望對您有用。

10年積累的網(wǎng)站制作、做網(wǎng)站經(jīng)驗,可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先網(wǎng)站設(shè)計后付款的網(wǎng)站建設(shè)流程,更有浚縣免費網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
坑1
咱們廢話不用多說,直接看例子。
現(xiàn)象
dataFromDb := []int{1,2,3} //從數(shù)據(jù)庫取出來的數(shù)據(jù)
var finalData []*int //目標(biāo)數(shù)據(jù)
for _,i := range dataFromDb{
finalData = append(finalData, &i)
}
for _, final := range finalData{
fmt.Println(*final)
}上面的例子很簡單
- 從數(shù)據(jù)庫取出來數(shù)據(jù) 1,2,3,賦值給 dataFromDb。
- 循環(huán)遍歷dataFromDb賦值給最終的目標(biāo)數(shù)據(jù) finalData。
- 循環(huán)輸出目標(biāo)數(shù)據(jù)finalData。
直觀的感受,上面簡直是一段簡單的不能再簡單的代碼了,相信大家會脫口而出最后finalData的值是1,2,3,但是我們實際運行一下,結(jié)果輸出的卻是
~/Sites/test ? go run main.go
3
3
3結(jié)果輸出的全部都是3,顯然這與我們的認(rèn)知是不符合的,但是為什么會這樣呢?如果想弄清這個原理,首先我們得知道for range到底干了什么。
for range原理
要想了解一個函數(shù)的原理,最好的方式就是看源碼,我們來看一看for range到底干了什么。
源碼來自于 go 編譯器的 「gc.walkrange」, 編譯器對 for range 表達(dá)式的解析如下:
// a為原始slice
ha := a
hv1 := 0
// slice長度
hn := len(a)
v1 := 0
v2 := nil // for i,v := range 中的 v
for ; h1 < hn ; h1++ {
tmp := ha[hv1]
v1,v2 := hv1,tmp
}- 每一次for range,其實是先復(fù)制出來了一個副本ha,本質(zhì)上循環(huán)的其實是副本。
- for range中,go語言會額外創(chuàng)建一個新的 v2 變量存儲切片中的元素,「循環(huán)中使用的這個變量 v2 會在每一次迭代被重新賦值而覆蓋,賦值時也會觸發(fā)拷貝, 且循環(huán)中每次都使用的v2變量」。
回到問題
for _,i := range dataFromDb{
finalData = append(finalData, &i)
}對于i來說,相當(dāng)于 var i int,然后在循環(huán)的過程中 i=1,i=2,i=3 &i是指向i的地址,「所以&i是永遠(yuǎn)不會變的」。
- 第一次循環(huán) &i指向i,i的值是1。
- 第二次循環(huán) &i指向i,i的值變成2了,同時也把第一次循環(huán)的i的結(jié)果改成2了。
- 第三次循環(huán) &i指向i,i的值變成3了,同時也把前兩次循環(huán)的i的結(jié)果改成3了。
如何解決
其實解決辦法很簡單,引入「中間變量」即可,代碼改成下面這個樣子。
dataFromDb := []int{1,2,3}
var finalData []*int
for _,i := range dataFromDb{
temp := i //引入中間變量,每一次循環(huán)都重新開辟了一個temp的空間
finalData = append(finalData, &temp)
}
for _, final := range finalData{
fmt.Println(*final)
}代碼加入了「中間變量temp」temp:=i等價于。
var temp int
temp = 1- 第一次循環(huán) temp開辟了一塊空間,指向了i,temp的值為1。
- 第二次循環(huán) temp「重新開辟了一塊空間」,指向了i,temp的值為2,因為是重新開辟的空間,所以不會影響到上一次循環(huán)。
- 第三次循環(huán) 原理同上一步。
坑2
現(xiàn)象
s := []int{1, 2, 3}
for _, v := range s {
go func() {
fmt.Println(v) // 輸出結(jié)果3 3 3
}()
}
select {}大家可以想一想上面這段代碼會輸出什么
3
3
3輸出結(jié)果居然全部都是最后一個值,這是為什么呢?
原因
在沒有將變量 v 的拷貝值傳進(jìn)匿名函數(shù)之前,只能獲取最后一次循環(huán)的值,這是新手最容易遇到的坑。
解決辦法
解決辦法其實比較簡單,在閉包函數(shù)上增加參數(shù),并且與go rountine綁定即可。
s := []int{1, 2, 3}
for _, v := range s {
go func(v int) {
fmt.Println(v) // 輸出結(jié)果3 1 2
}(v)
}
select {} 分享文章:原來Golang的Foreach這么坑!
本文路徑:http://www.dlmjj.cn/article/djsdghs.html


咨詢
建站咨詢
