for range
遍歷是go語言中經常使用的循環結構之一,在使用循環賦值時有時候須要注意指針的引用問題。在探討以前,先讓咱們來回顧下Go的指針。python
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
有時候當變量值很大,在函數中傳來傳去,就會佔用大量的內存空間。指針中存了指向變量值的地址,在必定程度上可使用指針來代替變量值,這樣在函數參數傳遞、各類邏輯計算中,就能夠節省大量空間。markdown
咱們來看一段示意代碼,代碼中在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,聊技術聊人生~