Go指针复制问题

``` package main import "fmt" type R struct { ID int CPU float32 MEM float32 } func main() { rr := GetR() var rs []*R for k, r := range *rr { fmt.Printf("%dth r, id: %d, cpu: %f, mem: %f\n", k, r.ID, r.CPU, r.MEM) rs = append(rs, &r) } fmt.Println(rs) iter(&rs) } func NewR(i int, c, m float32) *R { return &R{ ID: i, CPU: c, MEM: m, } } func iter(rs *[]*R) { for _, r := range *rs { fmt.Printf("id: %d, cpu: %f, mem: %f\n", r.ID, r.CPU, r.MEM) } } func GetR() *[]R { rr := &[]R{ *NewR(0, 4.0, 16000.0), *NewR(1, 2.0, 8000.0), *NewR(2, 1.0, 4000.0), } return rr } ``` 这段代码输出为什么是这个?希望麻烦大家帮忙解答下。https://github.com/dockerq/go-pointer-trick ``` 0th r, id: 0, cpu: 4.000000, mem: 16000.000000 1th r, id: 1, cpu: 2.000000, mem: 8000.000000 2th r, id: 2, cpu: 1.000000, mem: 4000.000000 [0xc42000e2a0 0xc42000e2a0 0xc42000e2a0] id: 2, cpu: 1.000000, mem: 4000.000000 id: 2, cpu: 1.000000, mem: 4000.000000 id: 2, cpu: 1.000000, mem: 4000.000000 ```
已邀请:
```go
for k, r := range *rr {
fmt.Printf("%dth r, id: %d, cpu: %f, mem: %f\n", k, r.ID, r.CPU, r.MEM)
rs = append(rs, &r)
}
```

这里的 r 一直是同一个地址的值的,for 循环的每次是覆盖旧的 r,你要用 *rr[k]

xkey - go

赞同来自: adolphlwq

刚刚评论里的代码经过markdown处理有问题,重新复制代码,放到前一个评论的回复里了

xkey - go

package main

import "fmt"

type R struct {
ID int
CPU float32
MEM float32
}

func main() {
rr := GetR()
var rs []*R
for k, r := range rr {
fmt.Printf("%dth r, id: %d, cpu: %f, mem: %f\n", k, r.ID, r.CPU, r.MEM)
rs = append(rs, r)
}
fmt.Println(rs)
iter(&rs)
}

func NewR(i int, c, m float32) *R {
return &R{
ID: i,
CPU: c,
MEM: m,
}
}

func iter(rs *[]*R) {
for _, r := range *rs {
fmt.Printf("id: %d, cpu: %f, mem: %f\n", r.ID, r.CPU, r.MEM)
}
}

func GetR() []*R {
rr := []*R{
NewR(0, 4.0, 16000.0),
NewR(1, 2.0, 8000.0),
NewR(2, 1.0, 4000.0),
}
return rr
}

adolphlwq - github.com/adolphlwq

```
for k, r := range *rr {
fmt.Printf("%dth r, id: %d, cpu: %f, mem: %f\n", k, r.ID, r.CPU, r.MEM)
rs = append(rs, &r)
}
```

关键在`for...range`,for循环中的**k,r**是临时变量,它们在确定之后`地址不会改变`,但是值会改变。`rs = append(rs, &r)`这里`&r`取r的地址一直是同一个值,循环结束后r的值是最后一次循环的值。

[参考:聊聊Go中的Range关键字](https://xiaozhou.net/something-about-range-of-go-2016-04-10.html)
```go
package main

import "fmt"

func main(){

r := []int{1, 2, 3, 4, 5}
var v int
fmt.Printf("v: %p\n", &v)
for k, v := range r {
fmt.Printf("%p--%p\n", &k, &v)
}
}
```

测试发现在for循环中,k, v并没有创建新的变量。

测试结果:

![](C:\Users\7hell\Desktop\3851.png)
k,v :=range x本质上是声明两个变量k ,v (x为数组或者slice,k为下标。x为map,k为key)。遍历x的时候每次将x当前元素的值赋值给v。所以v的地址是一直不变的。为了更好的理解。用三种方式改一下你这个例子,使他们输出正确的结果。
* 第一种 :最不推荐的一种,每次循环将r的值赋值给一个新的变量,注意是t:=r 并不是t :=&r ,将r的值给t而不是指针。rs = append(rs, &t)后。实际上rs每次append的是一个r的值拷贝的地址。

```
package main

import "fmt"

type R struct {
ID int
CPU float32
MEM float32
}

func main() {
rr := GetR()
var rs []*R
for k, r := range *rr {
fmt.Printf("%dth r, id: %d, cpu: %f, mem: %f\n", k, r.ID, r.CPU, r.MEM)
t := r
rs = append(rs, &t)
}
fmt.Println(rs)
iter(&rs)
}

func NewR(i int, c, m float32) *R {
return &R{
ID: i,
CPU: c,
MEM: m,
}
}

func iter(rs *[]*R) {
for _, r := range *rs {
fmt.Printf("id: %d, cpu: %f, mem: %f\n", r.ID, r.CPU, r.MEM)
}
}

func GetR() *[]R {
rr := &[]R{
*NewR(0, 4.0, 16000.0),
*NewR(1, 2.0, 8000.0),
*NewR(2, 1.0, 4000.0),
}
return rr
}
```

* 第二种:不使用r,直接使用索引赋值,这样总不会取到r的地址了吧。

```
package main

import "fmt"

type R struct {
ID int
CPU float32
MEM float32
}

func main() {
rr := GetR()
var rs []*R
for k, r := range *rr {
fmt.Printf("%dth r, id: %d, cpu: %f, mem: %f\n", k, r.ID, r.CPU, r.MEM)
rs = append(rs, &(*rr)[k])
}
fmt.Println(rs)
iter(&rs)
}

func NewR(i int, c, m float32) *R {
return &R{
ID: i,
CPU: c,
MEM: m,
}
}

func iter(rs *[]*R) {
for _, r := range *rs {
fmt.Printf("id: %d, cpu: %f, mem: %f\n", r.ID, r.CPU, r.MEM)
}
}

func GetR() *[]R {
rr := &[]R{
*NewR(0, 4.0, 16000.0),
*NewR(1, 2.0, 8000.0),
*NewR(2, 1.0, 4000.0),
}
return rr
}
```
* 第三种:range中的元素如果都是指针的话,直接赋值好了。没必要像2中那样先转成指针再取值再转成指针。所以修改GetR方法,返回指针数组

```
package main

import "fmt"

type R struct {
ID int
CPU float32
MEM float32
}

func main() {
rr := GetR()
var rs []*R
for k, r := range *rr {
fmt.Printf("%dth r, id: %d, cpu: %f, mem: %f\n", k, r.ID, r.CPU, r.MEM)
rs = append(rs, r)
}
fmt.Println(rs)
iter(&rs)
}

func NewR(i int, c, m float32) *R {
return &R{
ID: i,
CPU: c,
MEM: m,
}
}

func iter(rs *[]*R) {
for _, r := range *rs {
fmt.Printf("id: %d, cpu: %f, mem: %f\n", r.ID, r.CPU, r.MEM)
}
}

func GetR() *[]*R {
rr := &[]*R{
NewR(0, 4.0, 16000.0),
NewR(1, 2.0, 8000.0),
NewR(2, 1.0, 4000.0),
}
return rr
}
```

如果你能理解上面那三种方式,我觉得对于指针就有一个比较清晰的认识了。感觉你这里迷惑,问题不是出现在range这里。而是对于指针的理解不够深刻。尤其是我看你func GetR() *[]R这个方法的实现,简直无法理解为什么要这么写。
推荐看 [《golang for语句完全指南》](https://sheepbao.github.io/post/golang_for_range_complete_guide/)

一下引用其中的一段介绍:

>for语句的内部实现-array
golang的for语句,对于不同的格式会被编译器编译成不同的形式,如果要弄明白需要看 golang的编译器和相关数据结构的源码, 数据结构源码还好,但是编译器是用C++写的,本人C++是个弱鸡,这里只讲array内部实现。

```go
// The loop we generate:
// len_temp := len(range)
// range_temp := range
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = range_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }

// 例如代码:
array := [2]int{1,2}
for k,v := range array {
f(k,v)
}

// 会被编译成:
len_temp := len(array)
range_temp := array
for index_temp = 0; index_temp < len_temp; index_temp++ {
value_temp = range_temp[index_temp]
k = index_temp
v = value_temp
f(k,v)
}
```
> 所以像遍历一个数组,最后生成的代码很像C语言中的遍历,而且有两个临时变量index_temp,value_temp, 在整个遍历中一直复用这两个变量。所以会导致开头问题2的问题(详细解答会在后边)。

要回复问题请先登录注册