溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Go?for?range中容易踩的坑有哪些

發(fā)布時間:2023-03-21 14:32:35 來源:億速云 閱讀:104 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹了Go for range中容易踩的坑有哪些的相關(guān)知識,內(nèi)容詳細(xì)易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Go for range中容易踩的坑有哪些文章都會有所收獲,下面我們一起來看看吧。

1. for+傳值

先來到開胃菜,熱熱身~

type student struct {
  name string
  age  int
}

func main() {
  m := make(map[string]student)
  stus := []student{
    {name: "張三", age: 18},
    {name: "李四", age: 23},
    {name: "王五", age: 26},
  }
  for _, stu := range stus {
    m[stu.name] = stu
  }
  for k, v := range m {
    fmt.Println(k, "=>", v.name)
  }
}

不出意料,輸出結(jié)果為

李四 => 李四
王五 => 王五
張三 => 張三

這題比較簡單,就是簡單的傳值操作,大家應(yīng)該都能答上來。下面加大難度,改為傳址操作

2. for+傳址

將案例一改為傳址操作

type student struct {
  name string
  age  int
}

func main() {
  m := make(map[string]*student)
  stus := []student{
    {name: "張三", age: 18},
    {name: "李四", age: 23},
    {name: "王五", age: 26},
  }
  for _, stu := range stus {
    m[stu.name] = &stu
  }
  for k, v := range m {
    fmt.Println(k, "=>", v.name)
  }
}

好好想想應(yīng)該輸出什么結(jié)果呢?還是跟案例一是一樣的結(jié)果嗎?難道會有坑?

不出意料,還是出了意外,輸出結(jié)果為

張三 => 王五
李四 => 王五
王五 => 王五

為什么呢?

  • 首先,關(guān)鍵點(diǎn)在于Go的for循環(huán),對循環(huán)變量stu每次是循環(huán)并不是迭代(簡單的說,就是對循環(huán)變量stu只會做一次聲明和內(nèi)存地址的分配,后面循環(huán)就是不斷更新值);

  • 所以,取址操作 &stu,其實(shí)都是取的同一個變量的地址,只是值被循環(huán)更新為最后一個元素的值;

  • 最終,輸出的v.name,都是最后一個元素的name為王五。

解決方案

在for循環(huán)中,做同名變量覆蓋stu:=stu(即重新聲明一個局部變量,做值拷貝,避免相互影響)

type student struct {
  name string
  age  int
}

func main() {
  m := make(map[string]*student)
  stus := []student{
    {name: "張三", age: 18},
    {name: "李四", age: 23},
    {name: "王五", age: 26},
  }
  for _, stu := range stus {
    stu := stu  //同名變量覆蓋
    m[stu.name] = &stu
  }
  for k, v := range m {
    fmt.Println(k, "=>", v.name)
  }
}

輸出結(jié)果:

張三 => 張三
李四 => 李四
王五 => 王五

3.for+閉包

在for循環(huán)里,做閉包操作,也是很容易掉坑的??纯聪旅孑敵鍪裁??

var prints []func()
for _, v := range []int{1, 2, 3} {
  prints = append(prints, func() { fmt.Println(v) })
}
for _, print := range prints {
  print()
}

一眼看過去,感覺是輸出1 2 3,但實(shí)際會輸出 3 3 3

為什么呢?

  • 首先,在分析了案例二后,我們知道了Go的for循環(huán)對循環(huán)變量v,其實(shí)每次是循環(huán)并不是迭代;

  • 然后,閉包=函數(shù)+引用環(huán)境,在同一個引用環(huán)境下,循環(huán)變量v的值會被不斷的覆蓋;

  • 所以最終,在打印時,輸出的v,都是最后一個值3。

解決方案

和案例二解決方案一樣,是在for循環(huán)中,做同名變量覆蓋v:=v

var prints []func()
for _, v := range []int{1, 2, 3} {
  v := v //同名變量覆蓋  
  prints = append(prints, func() { fmt.Println(v) })
}
for _, print := range prints {
  print()
}

輸出結(jié)果:

1
2
3

4. for+goroutine

在for循環(huán)里,起goroutine協(xié)程,也是很迷惑很容易掉坑的??纯聪旅孑敵鍪裁??

var wg sync.WaitGroup
strs := []string{"1", "2", "3", "4", "5"}
for _, str := range strs {
  wg.Add(1)
  go func() {
    defer wg.Done()
    fmt.Println(str)
  }()
}
wg.Wait()

一眼看過去,感覺是會無序輸出1 2 3 4 5,但實(shí)際會輸出 5 5 5 5 5

為什么呢?

  • 首先,要記得Go的for循環(huán)對循環(huán)變量str,其實(shí)每次是循環(huán)并不是迭代;

  • 然后,main協(xié)程會和新起的協(xié)程做相互博弈,看誰執(zhí)行更快,按這個案例執(zhí)行情況來看,main協(xié)程執(zhí)行速度明顯比新起的協(xié)程會更快,所以str被更新為最后一個元素值5(備注:并非絕對);

  • 最終,在新起的協(xié)程中,使用str時值都為5,作為結(jié)果去輸出;

  • 拓展:如果在新起協(xié)程前,sleep個5s,輸出結(jié)果又會截然不同,感興趣的同學(xué)可以自行實(shí)驗(yàn)下,然后逐步深入地了解下GMP調(diào)度機(jī)制。

解決方案

和前面兩個案例解決方案一樣,是在for循環(huán)中,做同名變量覆蓋str:=str

var wg sync.WaitGroup
strs := []string{"1", "2", "3", "4", "5"}
for _, str := range strs {
  str := str //同名變量覆蓋
  wg.Add(1)
  go func() {
    defer wg.Done()
    fmt.Println(str)
  }()
}
wg.Wait()

輸出結(jié)果:

5
4
2
1
3

注意是1~5無序輸出

關(guān)于“Go for range中容易踩的坑有哪些”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對“Go for range中容易踩的坑有哪些”知識都有一定的了解,大家如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI