Go Tips|for range 深刻分析指針取值排坑

for range 遍歷是go語言中經常使用的循環結構之一,在使用循環賦值時有時候須要注意指針的引用問題。在探討以前,先讓咱們來回顧下Go的指針。數組

Go 指針回顧

Go 語言中有指針類型,沒有指針的計算,這在必定程度上削弱了指針的功能,但也減小了指針的複雜度,給使用者帶了更好的使用體驗。在Go 中,類型 *T 是指向類型T的值的指針,&符號會生成一個指向其做用對象的指針,*符號表示指針指向的底層的值。以下:bash

var p *int      // 定義一個指針p 
i := 42         // 初始化一個整數類型 i 
p = &i          // p指針指向i,或者說將 i 的指針賦值給p  
fmt.Println(*p) // 經過指針 p 讀取 i
*p = 21         // 經過指針 p 設置 i

可參考以下圖理解:app

pointer

有時候當變量值很大,在函數中傳來傳去,就會佔用大量的內存空間。指針中存了指向變量值的地址,在必定程度上可使用指針來代替變量值,這樣在函數參數傳遞、各類邏輯計算中,就能夠節省大量空間。函數

for range 遍歷取值的問題

咱們來看一段示意代碼,代碼中在append賦值的時候爲了節省內存使用了指針。猜猜遍歷結果集是什麼?spa

package main

import "fmt"

type Person struct {
    name string
}

func main() {
    arr := []Person{
        Person{"小明"},
        Person{"小剛"},
    }
    var res []*Person

    for _, v := range arr {
        res = append(res, &v)
    }
    // 遍歷查看結果集
    for _, person := range res{
        fmt.Println("name-->:", person.name)
    }
}

看結果:指針

name-->: 小剛
name-->: 小剛

能夠看到在遍歷 arr 這個數組時,後邊的值把前邊的覆蓋了。讓咱們加點打印信息:code

···
    for _, v := range arr {
        fmt.Printf("v 指針 %p\n", &v)
        fmt.Println("v 的值", v)
        res = append(res, &v)
  }
  fmt.Println(res)
···

輸出以下:對象

v 指針 0xc0001101e0
v 的值 {小明}
v 指針 0xc0001101e0
v 的值 {小剛}
[0xc0001101e0 0xc0001101e0]

能夠看到在結果集中的指針是同樣的,也就是說在 for range 的時候,v 只初始化了一次,以後的遍歷都是在原來遍歷的基礎上賦值,全部v的指針(地址)並無變。該指針指向的是最後一次遍歷的v的值,因此最後結果集中,也就都成了最後遍歷的v的值。blog

這裏正確的作法是使用下標。以下:內存

for i, _ := range arr {
        fmt.Printf("v 指針 %p\n", &arr[i])
        fmt.Println("v 的值", arr[i])
        res = append(res, &arr[i])
    }

結果:

v 指針 0xc0000a6020
v 的值 {小明}
v 指針 0xc0000a6030
v 的值 {小剛}
[0xc0000a6020 0xc0000a6030]
name-->: 小明
name-->: 小剛

總結

Go 語言中的指針在必定程度上能夠節省內存的佔用,但在使用時必定要注意先後語言環境中是否有變更,以避免形成引用同值的鍋。

相關文章
相關標籤/搜索