自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

原來Golang的Foreach這么坑!

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

前言

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

坑1

咱們廢話不用多說,直接看例子。

現(xiàn)象

dataFromDb := []int{1,2,3} //從數(shù)據(jù)庫取出來的數(shù)據(jù)
 
  var finalData []*int //目標數(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賦值給最終的目標數(shù)據(jù) finalData。
  • 循環(huán)輸出目標數(shù)據(jù)finalData。

直觀的感受,上面簡直是一段簡單的不能再簡單的代碼了,相信大家會脫口而出最后finalData的值是1,2,3,但是我們實際運行一下,結(jié)果輸出的卻是

~/Sites/test ? go run main.go                                                                                  
3
3
3

結(jié)果輸出的全部都是3,顯然這與我們的認知是不符合的,但是為什么會這樣呢?如果想弄清這個原理,首先我們得知道for range到底干了什么。

for range原理

要想了解一個函數(shù)的原理,最好的方式就是看源碼,我們來看一看for range到底干了什么。

源碼來自于 go 編譯器的 「gc.walkrange」, 編譯器對 for range 表達式的解析如下:

// 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,其實是先復制出來了一個副本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來說,相當于 var i int,然后在循環(huán)的過程中 i=1,i=2,i=3 &i是指向i的地址,「所以&i是永遠不會變的」。

  • 第一次循環(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 的拷貝值傳進匿名函數(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 {}
責任編輯:姜華 來源: 程序員小飯
相關(guān)推薦

2019-03-14 09:29:02

Linux系統(tǒng)內(nèi)存

2021-04-19 05:42:51

Mmap文件系統(tǒng)

2023-11-01 14:49:07

2020-09-24 06:44:54

HTTPS網(wǎng)站 HTTP

2022-12-06 17:30:04

2023-10-30 08:16:33

數(shù)據(jù)庫插件Mybatis

2021-02-07 08:13:18

@DateTimeFo@NumberFormSpring

2022-10-21 08:17:13

MongoDB查詢Document

2023-09-22 08:00:00

分布式鎖Redis

2020-11-27 10:34:01

HTTPHTTPS模型

2021-04-19 07:35:01

Linuxhistory命令

2024-03-12 08:44:56

WebWorkerTypeScript語法

2021-04-26 10:24:52

Linux 開發(fā)操作系統(tǒng)

2014-10-08 15:00:50

SUSE操作系統(tǒng)云計算

2021-08-29 18:13:03

緩存失效數(shù)據(jù)

2019-03-15 10:55:12

通信系統(tǒng)手機

2022-11-02 19:08:48

微服務輪詢消費者

2023-07-26 00:32:33

注解抽象spring

2021-02-17 21:04:03

Ehcache緩存Java

2017-02-22 14:09:31

Javaforeach反編譯
點贊
收藏

51CTO技術(shù)棧公眾號