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

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

Go 指針回顧

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

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

可參考以下圖理解:bash

pointer

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

for range 遍歷取值的問題

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

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 這個數組時,後邊的值把前邊的覆蓋了。讓咱們加點打印信息:學習

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

輸出以下:ui

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

複製代碼

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

這裏正確的作法是使用下標。以下:3d

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

我是DeanWu,一個努力成爲真正SRE的人。


關注公衆號「碼農吳先生」, 可第一時間獲取最新文章。回覆關鍵字「go」「python」獲取我收集的學習資料,也可回覆關鍵字「小二」,加我wx,聊技術聊人生~

相關文章
相關標籤/搜索