for range 遍历是go语言中罕用的循环构造之一,在应用循环赋值时有时候须要留神指针的援用问题。在探讨之前,先让咱们来回顾下Go的指针。

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

可参考如下图了解:

有时候当变量值很大,在函数中传来传去,就会占用大量的内存空间。指针中存了指向变量值的地址,在肯定水平上能够应用指针来代替变量值,这样在函数参数传递、各种逻辑计算中,就能够节俭大量空间。

for range 遍历取值的问题

咱们来看一段示意代码,代码中在append赋值的时候为了节俭内存应用了指针。猜猜遍历后果集是什么?

package mainimport "fmt"type Persion struct {    name string}func main() {    arr := []Persion{        Persion{"小明"},        Persion{"小刚"},    }    var res []*Persion    for _, v := range arr {        res = append(res, &v)    }    // 遍历查看后果集    for _, persion := range res{        fmt.Println("name-->:", persion.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)···

输入如下:

v 指针 0xc0001101e0v 的值 {小明}v 指针 0xc0001101e0v 的值 {小刚}[0xc0001101e0 0xc0001101e0]

能够看到在后果集中的指针是一样的,也就是说在 for range 的时候,v 只初始化了一次,之后的遍历都是在原来遍历的根底上赋值,所有v的指针(地址)并没有变。该指针指向的是最初一次遍历的v的值,所以最初后果集中,也就都成了最初遍历的v的值。

这里正确的做法是应用下标。如下:

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

后果:

v 指针 0xc0000a6020v 的值 {小明}v 指针 0xc0000a6030v 的值 {小刚}[0xc0000a6020 0xc0000a6030]name-->: 小明name-->: 小刚

总结

Go 语言中的指针在肯定水平上能够节俭内存的占用,但在应用时肯定要留神前后语言环境中是否有变动,免得造成援用同值的锅。