日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
原來Golang的Foreach這么坑!

前言

用過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