Golang for-range取地址&操作踩坑记录

698 阅读2分钟

话不多说,咱们直接上问题,先看一段代码:

//随便定义的,当个例子
type Item struct {
   key int
}

func main() {
   var all []*Item
   items := []Item{
      {key: 1},
      {key: 2},
      {key: 3},
      {key: 4},
   }
   for _, v := range items {
      all = append(all, &v)
   }
   fmt.Printf("%v", all)
}

大家可以先看看最后printf应该输出什么?好了,答案揭晓

[0xc0000aa078 0xc0000aa078 0xc0000aa078 0xc0000aa078]

为什么all中保存的是同一个地址呢,这个地址指向的数据是什么?那我们重新打印一遍

for _, v := range all {
   fmt.Printf("%v, %p\n", *v, v)
}

//Output:
/*
{4}, 0xc0000aa078                                    
{4}, 0xc0000aa078                                    
{4}, 0xc0000aa078                                    
{4}, 0xc0000aa078
*/

我们发现,此时all中保存的都是最后一个Item,我们明明是for-range遍历切片,为什么会是这种结果?

那我们再换一种方式,我们先不进行append,我们就单纯的遍历一下

type Item struct {
   key int
}

func main() {
   var all []*Item
   items := []Item{
      {key: 1},
      {key: 2},
      {key: 3},
      {key: 4},
   }
   for _, v := range items {
      fmt.Printf("%v, %p\n", v, &v)
   }
}

//Output:
//{1}, 0xc0000aa058
//{2}, 0xc0000aa058                                    
//{3}, 0xc0000aa058                                    
//{4}, 0xc0000aa058  

从结果发现,数据是正确的,但是每次遍历时v的地址却都是一样的。为了便于我们观察,我们更详细的打印一下

for i, v := range items {
   fmt.Printf("%p, %v, %p\n", &items[i], v, &v)
}

//Output:
// 0xc0000a8080, {1}, 0xc0000aa058
// 0xc0000a8088, {2}, 0xc0000aa058                      
// 0xc0000a8090, {3}, 0xc0000aa058                      
// 0xc0000a8098, {4}, 0xc0000aa058  

情况已经很清晰了,第一个地址&items[i]不同这是正确的,遍历出的值{1}、{2}、{3}、{4}这也没有毛病,只有v的地址是相同的这一点让人疑惑

那我们可以大胆猜想一下,是不是for-range在遍历时每次循环创建的v都是同一个,然后再将每次循环到的数据保存到这个v

type Item struct {
   key int
}

func main() {
   var all []*Item
   items := []Item{
      {key: 1},
      {key: 2},
      {key: 3},
      {key: 4},
   }
   for _, v := range items {
      all = append(all, &v)
   }
   fmt.Printf("%v", all)
}

此时我们回到最开始的问题,由于在for-range遍历时创建的v是同一个v,那么在将其添加到指针切片中后,保存的都是每次循环最后一个值,这也就是为什么all中保存的值地址相同且数据都是遍历切片的最后一个