Golang for range 和闭包的坑

shensi · · 2454 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

#### 一、for range 坑: 例子:将数组元素的地址存入到指针map中 上代码: ``` arr := []int{1, 2, 3} // 普通数组 m := make(map[int]*int) // 指针map for i, v := range arr { // fmt.Println(&v) // 如果在这里打印v的内存地址的话,会发现3次的地址都是一样的 m[i] = &v } for _, v := range m { fmt.Println(*v) // 输出数据 3 3 3 } ``` 可以看出输出结果,并非预期那样 1 2 3. 从输出的数据可以推测,好像每次都输出了最的原始3 。 ** 原因是什么:** ``` 其实原因是循环变量的作用域的规则限制。在上面的程序中,v 在 for 循环引进的一个块作用域内进行声明。在循环里创建的所有函数变量共享相同的变量,就是一个可访问的存储位置,而不是固定的值。 假设 v 变量的地址在 0xc0000180c0 上, for 循环在迭代过程中,所有变量值都是在这地址上迭代的。因此存入map中的地址3次都是一样的 0xc0000180c0 ``` #### 如何解决 ``` arr := []int{1, 2, 3} // 普通数组 m := make(map[int]*int) // 指针map for i, v := range arr { fmt.Println(&v) vTmp := v // 修改点 m[i] = &vTmp // 修改点 } for _, v := range m { fmt.Println(*v) // 输出数据 3 3 3 } ``` 在上面的程序中,for循环语句引入了新的词法块,循环变量v在这个词法块中被声明。在该循环中生成的所有函数值都共享相同的循环变量。需要注意,函数值中记录的是循环变量的内存地址,而不是循环变量某一时刻的值。以v为例,后续的迭代会不断更新v的值,当操作执行时,for循环已完成,v中存储的值等于最后一次迭代的值。这意味着,每次都是相同的目录。 通常,为了解决这个问题,我们会引入一个与循环变量同名的局部变量,作为循环变量的副本。比如下面的变量dir,虽然这看起来很奇怪,但却很有用。 #### 二、for range 使用闭包 坑: 错误示例: ``` s := []int{1, 2, 3} for _, v := range s { go func() { fmt.Println(v) // 输出结果3 3 3 }() } select {} ``` 正确使用 ``` s := []int{1, 2, 3} for _, v := range s { go func(v int) { fmt.Println(v) // 输出结果3 1 2 }(v) } select {} ``` ** 原因:** ``` 在没有将变量 v 的拷贝值传进匿名函数之前,只能获取最后一次循环的值,这是新手最容易遇到的坑。 ``` #### 三、 匿名函数列表: 错误示例: ``` var slice []func() // 匿名函数列表 sli := []int{1, 2, 3, 4, 5} for _, v := range sli { fmt.Println(&v) slice = append(slice, func() { fmt.Println(v * v) // 直接打印结果 }) } for _, val := range slice { val() } ``` 输出结果: ``` 0xc0000b2008 0xc0000b2008 0xc0000b2008 0xc0000b2008 0xc0000b2008 25 25 25 25 25 ``` 修改后代码: ``` var slice []func() // 匿名函数列表 sli := []int{1, 2, 3, 4, 5} for _, v := range sli { t := v fmt.Println(&t) // 内存次之不同 slice = append(slice, func() { fmt.Println(t * t) // 直接打印结果 }) } for _, val := range slice { val() } ``` 输出结果: ``` 0xc0000b2008 0xc0000b2020 0xc0000b2028 0xc0000b2030 0xc0000b2038 1 4 9 16 25 ``` ** 原因:** ``` 每次 append 操作仅将匿名函数放入到列表中,但并未执行,并且引用的变量都是 v,随着 v 的改变匿名函数中的 v 也在改变,所以当执行这些函数时,他们读取的都是环境变量 v 最后一次的值。解决的方法就是每次复制变量 v 然后传到匿名函数中,让闭包的环境变量不相同。 ``` #### 四、defer调用闭包 ``` package main import "fmt" func main() { x, y := 1, 2 defer func(a int) { fmt.Printf("x:%d,y:%d\n", a, y) // y 为闭包引用 }(x) // 复制 x 的值 x += 100 y += 100 fmt.Println(x, y) } ``` 输出结果: ``` 101 102 x:1,y:102 ``` 这不是go或defer本身导致的,而是因为它们都会等待循环结束后,再执行函数值。 *** x和y的值代表了复制与引用的区别,什么时候使用复制什么时候使用引用需要谨慎考虑。*** 参考文档: [简书-田飞雨](https://www.jianshu.com/p/fa21e6fada70) [简书-gurlan](https://www.jianshu.com/p/0e2e353b6c7d)

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2454 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传