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

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


咨詢
建站咨詢
