for range
遍歷是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
有時候當變量值很大,在函數中傳來傳去,就會佔用大量的內存空間。指針中存了指向變量值的地址,在必定程度上可使用指針來代替變量值,這樣在函數參數傳遞、各類邏輯計算中,就能夠節省大量空間。函數
咱們來看一段示意代碼,代碼中在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 語言中的指針在必定程度上能夠節省內存的佔用,但在使用時必定要注意先後語言環境中是否有變更,以避免形成引用同值的鍋。