#### 一、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)
有疑问加站长微信联系(非本文作者))