目录

golang for-range 使用

简介

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)
    }()
}

WeChat Pay
关注微信公众号,可了解更多云原生详情~