Go中for range的一个坑

for range是值拷贝出来的副本

在使用for range的时候,要注意的是,不管是slice还是map,循环的值都是被range值拷贝出来的副本值。
举个简单的例了

package main

import "fmt"

type Test struct {
    Index int
    Num   int
}

func main() {
    var t []Test
    t = append(t,Test{Index:1,Num:1})
    t = append(t,Test{Index:2,Num:2})

    for _, v := range t {
        v.Num += 100
    }

    for _,v:= range t{
        fmt.Println(v.Index,v.Num)
    }
}

得到的结果是

1 1
2 2

因为这里的v已经不是原来t中的了,而是值拷贝出来的副本值,副本值,副本值!
当然,map中的结果也是一样

m := make(map[int]Test)
for _, v := range m {
    v.Num += 100
}

为了防止这个问题,我们可以用指针数组

var t []*Test
t = append(t,&Test{Index:1,Num:1})
t = append(t,&Test{Index:2,Num:2})

for _, v := range t {
    v.Num += 100
}

这样输出的结果是

1 101
2 102

对于数组还有更好的办法,就是用for range,但value值用”_”舍弃了元素的复制,用下标去访问

for i, _ := range t {
    t[i].Num += 100
}

这样的结果也是

1 101
2 102

偷偷的告诉你,这样的效率还比for _, v := range t 要高哦
为什么会出现这种情况呢,我们去看一下for range 原理就清楚了

for range 原理

通过查看https://github.com/golang/gofrontend源代码,我们可以发现for range的实现是:

# statements.cc:6419 (441f3f1 on 4 Oct)
// Arrange to do a loop appropriate for the type.  We will produce
//   for INIT ; COND ; POST {
//           ITER_INIT
//           INDEX = INDEX_TEMP
//           VALUE = VALUE_TEMP // If there is a value
//           original statements
//   }

并且对于Slice,Map等各有具体不同的编译实现,我们先看看for range slice的具体实现

# statements.cc:6638  (441f3f1 on 4 Oct)
  // The loop we generate:
  //   for_temp := range
  //   len_temp := len(for_temp)
  //   for index_temp = 0; index_temp < len_temp; index_temp++ {
  //           value_temp = for_temp[index_temp]
  //           index = index_temp
  //           value = value_temp
  //           original body
  //   }

先是对要遍历的 Slice 做一个拷贝,获取长度大小,然后使用常规for循环进行遍历,并且返回值的拷贝。
再看看for range map的具体实现:

# statements.cc:6891  (441f3f1 on 4 Oct)
  // The loop we generate:
  //   var hiter map_iteration_struct
  //   for mapiterinit(type, range, &hiter); hiter.key != nil; mapiternext(&hiter) {
  //           index_temp = *hiter.key
  //           value_temp = *hiter.val
  //           index = index_temp
  //           value = value_temp
  //           original body
  //   }

也是先对map进行了初始化,因为map是hashmap,所以这里其实是一个hashmap指针的拷贝。
不多说了,躺过这个坑的人只想静静。。。

0%