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指针的拷贝。
不多说了,躺过这个坑的人只想静静。。。