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
已邀请:
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关键字

lys86_1205

赞同来自:

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并没有创建新的变量。

测试结果:

novacaine

赞同来自:

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这个方法的实现,简直无法理解为什么要这么写。

qiyin

赞同来自:

推荐看 《golang for语句完全指南》

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

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

// 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的问题(详细解答会在后边)。

要回复问题请先登录注册