简介
go 语言的 for - range 作为 for 的语法糖,用起来确实舒服,但是却有不少坑在当中。
问题:
1、使用指针获取元素
如下代码想从数组遍历获取一个指针元素切片集合
1
2
3
4
5
6
7
8
9
|
arr := [2]int{1, 2}
res := []*int{}
for _, v := range arr {
res = append(res, &v)
}
for _, v := range res {
fmt.println(*v)
}
// output: 2 2
|
结果是2 2 , 不符合预期。通过阅读go 编译器源码,发现会生成一个全局变量,在循环过程中,这个每次遍历的值都会赋值给这个全局变量,所以上例中使用指针获取值的地址追加到数组中,始终都是这个全局变量的地址,所以地址是一样的。这个地址遍历完最后指向了数组最后一个元素,所以打印的是数组最后一个元素。
1
2
3
4
5
6
7
8
9
|
// len_temp := len(range)
// range_temp := range
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// 这个 value_temp 就是上面说的全局变量
// value_temp = range_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }
|
出现这种情况如何修改呢?
方法一:使用局部变量拷贝 v
1
2
3
4
5
6
|
for _, v := range arr {
//局部变量v替换了v,也可用别的局部变量名, 这样 &v 就不是for - range 创建的全局变量地址了
//保证每个地址不同
v := v
res = append(res, &v)
}
|
方法二:使用索引获取值
1
2
3
4
|
//这种其实退化为for循环的简写
for k := range arr {
res = append(res, &arr[k])
}
|
2、遍历过程中扩容数组
1
2
3
4
|
v := []int{1, 2, 3}
for i := range v {
v = append(v, i)
}
|
虽然在遍历过程中,不断给切片增加元素,但是根据go编译器源码,可以发现在遍历之前,数组长度已经固定,所以这个循环会正常结束。
1
2
3
4
5
6
7
8
9
10
|
// 切片长度已经在遍历前固定
// len_temp := len(range)
// range_temp := range
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// 这个 value_temp 就是上面说的全局变量
// value_temp = range_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }
|
3、对大数组遍历
1
2
3
4
|
var arr = [102400]int{1, 1, 1}
for i, n := range arr {
pass
}
|
对一个大数组使用 for range 遍历的话,会浪费内存资源,因为在遍历前,会拷贝一次数组。
1
2
3
4
5
6
7
8
9
|
// 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
// }
|
可以使用遍历数组的指针,或者对数组转为切片
1
2
|
for i, n := range &arr
for i, n := range arr[:]
|
4、goroutine 加 闭包
1
2
3
4
5
6
7
8
|
var m = []int{1, 2, 3}
for i := range m {
go func() {
fmt.Print(i)
}()
}
//block main 1ms to wait goroutine finished
time.Sleep(time.Millisecond)
|
预期输出0,1,2的某个组合,如012,210.. 结果是222. 也是拷贝的问题,因为闭包 = 函数 + 环境
这里闭包函数引用了 i,是指针
使用参数传入闭包内,这样 i 进行了值拷贝
1
2
3
4
5
|
for i := range m {
go func(i int) {
fmt.Print(i)
}(i)
}
|
使用局部变量拷贝
1
2
3
4
5
6
|
for i := range m {
i := i
go func() {
fmt.Print(i)
}()
}
|
关注微信公众号,可了解更多云原生详情~