go語言中這個for-range的問題可必定要知道呀

前言

讀者A:不會吧,阿Sir,這周這麼高產~~~面試

asong:固然啦,爲了大家,一切都值得~~~設計模式

讀者B:淨放臭屁屁,就你戲多~~~架構

asong:你兇人家,壞壞~~~app

哈哈哈,戲太足了奧。自導自演可還行。今日分享以前,先放鬆放鬆嘛,畢竟接下來的知識,仍是須要咱們思考的。今天給你們分享的是go中的range,這個咱們在實際開發中,是常用,可是他有一個坑,使用很差,是要被開除的。可是,今天你剛好看了我這一篇文章,就避免了這個坑,開心嘛~~~。直接笑,別克制,我知道你嘴角已經上揚了。框架

廢話結束,咱們直接開始。微服務

正文

1. 指針數據坑

range到底有什麼坑呢,咱們先來運行一個例子吧。ui

package main

import (
    "fmt"
)

type user struct {
    name string
    age uint64
}

func main()  {
    u := []user{
        {"asong",23},
        {"song",19},
        {"asong2020",18},
    }
    n := make([]*user,0,len(u))
    for _,v := range u{
        n = append(n, &v)
    }
    fmt.Println(n)
    for _,v := range n{
        fmt.Println(v)
    }
}

這個例子的目的是,經過u這個slice構形成新的slice。咱們預期應該是顯示uslice的內容,可是運行結果以下:spa

[0xc0000a6040 0xc0000a6040 0xc0000a6040]
&{asong2020 18}
&{asong2020 18}
&{asong2020 18}

這裏咱們看到n這個slice打印出來的三個一樣的數據,而且他們的內存地址相同。這是什麼緣由呢?先彆着急,再來看這一段代碼,我給他改正確他,對比以後咱們再來分析,大家纔會恍然大悟。架構設計

package main

import (
    "fmt"
)

type user struct {
    name string
    age uint64
}

func main()  {
    u := []user{
        {"asong",23},
        {"song",19},
        {"asong2020",18},
    }
    n := make([]*user,0,len(u))
    for _,v := range u{
        o := v
        n = append(n, &o)
    }
    fmt.Println(n)
    for _,v := range n{
        fmt.Println(v)
    }
}

細心的大家看到,我改動了哪一部分代碼了嘛?對,沒錯,我就加了一句話,他就成功了,我在for range裏面引入了一箇中間變量,每次迭代都從新聲明一個變量o,賦值後再將v的地址添加n切片中,這樣成功解決了剛纔的問題。翻譯

如今來解釋一下緣由:在for range中,變量v是用來保存迭代切片所得的值,由於v只被聲明瞭一次,每次迭代的值都是賦值給v,該變量的內存地址始終未變,這樣講他的地址追加到新的切片中,該切片保存的都是同一個地址,這確定沒法達到預期效果的。這裏還須要注意一點,變量v的地址也並非指向原來切片u[2]的,因我在使用range迭代的時候,變量v的數據是切片的拷貝數據,因此直接copy告終構體數據。

上面的問題還有一種解決方法,直接引用數據的內存,這個方法比較好,不須要開闢新的內存空間,看代碼:

......略
for k,_ := range u{
        n = append(n, &u[k])
    }
......略

2. 迭代修改變量問題

仍是剛纔的例子,咱們作一點改動,如今咱們要對切片中保存的每一個用戶的年齡進行修改,由於咱們都是永遠18歲,嘎嘎嘎~~~。

package main

import (
    "fmt"
)

type user struct {
    name string
    age uint64
}

func main()  {
    u := []user{
        {"asong",23},
        {"song",19},
        {"asong2020",18},
    }
    for _,v := range u{
        if v.age != 18{
            v.age = 20
        }
    }
    fmt.Println(u)
}

來看一下運行結果:

[{asong 23} {song 19} {asong2020 18}]

哎呀,怎麼回事。怎麼沒有更改呢。其實道理都是同樣,還記得,我在上文說的一個知識點嘛。對,就是這個,想起來了吧。v變量是拷貝切片中的數據,修改拷貝數據怎麼會對原切片有影響呢,仍是這個問題,copy這個知識點很重要,一不注意,就會出現問題。知道問題了,咱們如今來把這個問題解決吧。

package main

import (
    "fmt"
)

type user struct {
    name string
    age uint64
}

func main()  {
    u := []user{
        {"asong",23},
        {"song",19},
        {"asong2020",18},
    }
    for k,v := range u{
        if v.age != 18{
            u[k].age = 18
        }
    }
    fmt.Println(u)
}

能夠看到,咱們直接對切片的值進行修改,這樣就修改爲功了。因此這裏仍是要注意一下的,防止之後出現bug

3. 是否會形成死循環

來看一段代碼:

func main() {
    v := []int{1, 2, 3}
    for i := range v {
        v = append(v, i)
    }
}

這一段代碼會形成死循環嗎?答案:固然不會,前面都說了range會對切片作拷貝,新增的數據並不在拷貝內容中,並不會發生死循環。這種題通常會在面試中問,能夠留意下的。

你不知道的range用法

delete

沒看錯,刪除,在range迭代時,能夠刪除map中的數據,第一次見到這麼使用的,我剛聽到確實不太相信,因此我就去查了一下官方文檔,確實有這個寫法:

for key := range m {
    if key.expired() {
        delete(m, key)
    }
}

看看官方的解釋:

The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next. If map entries that have not yet been reached are removed during iteration, the corresponding iteration values will not be produced. If map entries are created during iteration, that entry may be produced during the iteration or may be skipped. The choice may vary for each entry created and from one iteration to the next. If the map is nil, the number of iterations is 0.

翻譯:
未指定`map`的迭代順序,而且不能保證每次迭代之間都相同。 若是在迭代過程當中刪除了還沒有到達的映射條目,則不會生成相應的迭代值。 若是映射條目是在迭代過程當中建立的,則該條目可能在迭代過程當中產生或能夠被跳過。 對於建立的每一個條目以及從一個迭代到下一個迭代,選擇可能有所不一樣。 若是映射爲nil,則迭代次數爲0。

看這個代碼:

func main()  {
    d := map[string]string{
        "asong": "帥",
        "song": "太帥了",
    }
    for k := range d{
        if k == "asong"{
            delete(d,k)
        }
    }
    fmt.Println(d)
}

# 運行結果
map[song:太帥了]

從運行結果咱們能夠看出,key爲asong的這位帥哥被從帥哥map中刪掉了,哇哦,可氣呀。這個方法,相信不少小夥伴都不知道,今天教給大家了,之後能夠用起來了。

add

上面是刪除,那確定會有新增呀,直接看代碼吧。

func main()  {
    d := map[string]string{
        "asong": "帥",
        "song": "太帥了",
    }
    for k,v := range d{
        d[v] = k
        fmt.Println(d)
    }
}

這裏我把打印放到了range裏,大家思考一下,新增的元素,在遍歷時可以遍歷到呢。咱們來驗證一下。

func main()  {
    var addTomap = func() {
        var t = map[string]string{
            "asong": "太帥",
            "song": "好帥",
            "asong1": "很是帥",
        }
        for k := range t {
            t["song2020"] = "真帥"
            fmt.Printf("%s%s ", k, t[k])
        }
    }
    for i := 0; i < 10; i++ {
        addTomap()
        fmt.Println()
    }
}

運行結果:

asong太帥 song好帥 asong1很是帥 song2020真帥 
asong太帥 song好帥 asong1很是帥 
asong太帥 song好帥 asong1很是帥 song2020真帥 
asong1很是帥 song2020真帥 asong太帥 song好帥 
asong太帥 song好帥 asong1很是帥 song2020真帥 
asong太帥 song好帥 asong1很是帥 song2020真帥 
asong太帥 song好帥 asong1很是帥 
asong1很是帥 song2020真帥 asong太帥 song好帥 
asong太帥 song好帥 asong1很是帥 song2020真帥 
asong太帥 song好帥 asong1很是帥 song2020真帥

從運行結果,咱們能夠看出來,每一次的結果並非肯定的。這是爲何呢?這就來揭祕,map內部實現是一個鏈式hash表,爲了保證無順序,初始化時會隨機一個遍歷開始的位置,因此新增的元素被遍歷到就變的不肯定了,一樣刪除也是一個道理,可是刪除元素後邊就不會出現,因此必定不會被遍歷到。

總結

怎麼樣,夥伴們,收穫不小吧。一個小小的range就會引起這麼多的問題,因此說寫代碼必定要實踐,光靠想是沒有用的,有些問題只有在實踐中才會有所提升。但願今天的分享對大家有用,好啦,這一期就結束啦。咱們下期見。打個預告:下期將介紹go-elastic的使用,有須要的小夥伴留意一下。

結尾給你們發一個小福利吧,最近我在看[微服務架構設計模式]這一本書,講的很好,本身也收集了一本PDF,有須要的小夥能夠到自行下載。獲取方式:關注公衆號:[Golang夢工廠],後臺回覆:[微服務],便可獲取。

我翻譯了一份GIN中文文檔,會按期進行維護,有須要的小夥伴後臺回覆[gin]便可下載。

我是asong,一名普普統統的程序猿,讓我一塊兒慢慢變強吧。歡迎各位的關注,咱們下期見~~~

推薦往期文章:

相關文章
相關標籤/搜索