我正在学习围棋但无法理解
var rmdirs []func()
for _, dir := range tempDirs() {
os.MkdirAll(dir, 0755)
rmdirs = append(rmdirs, func() {
os.RemoveAll(dir) // NOTE: incorrect!
})
}
Run Code Online (Sandbox Code Playgroud)
书里的解释我看了好几遍,还是不明白为什么不对?
我记得在 go 中参数是按值传递的,所以每个循环dir都是不同的值,为什么不正确?
您的直觉是正确的:go 对迭代 values 重用相同的地址,因此无法保证dir附加到匿名函数时指向的值rmdirs与创建函数并dir首次捕获时所指向的值相同。 规范中的确切措辞是:
迭代变量可以使用短变量声明 (:=) 的形式由“范围”子句声明。在这种情况下,它们的类型被设置为各自迭代值的类型,它们的范围是“for”语句的块;它们在每次迭代中被重新使用。如果迭代变量是在“for”语句之外声明的,执行后它们的值将是最后一次迭代的值。
(强调我的)。为了进一步演示,这是您的代码尝试执行的操作的简化版本:
var rmdirs []func()
tempDirs := []string{"one", "two", "three", "four"}
for _, dir := range tempDirs {
fmt.Printf("dir=%v, *dir=%p\n", dir, &dir)
rmdirs = append(rmdirs, func() {
fmt.Printf("dir=%v, *dir=%p\n", dir, &dir)
})
}
fmt.Println("---")
for _, f := range rmdirs {
f()
}
Run Code Online (Sandbox Code Playgroud)
运行时,这会产生以下输出:
dir=one, *dir=0x40e128
dir=two, *dir=0x40e128
dir=three, *dir=0x40e128
dir=four, *dir=0x40e128
---
dir=four, *dir=0x40e128
dir=four, *dir=0x40e128
dir=four, *dir=0x40e128
dir=four, *dir=0x40e128
Run Code Online (Sandbox Code Playgroud)
游乐场链接:https : //play.golang.org/p/_rS8Eq9qShM
如您所见,在循环中每次迭代都会重用相同的地址。匿名函数的每次迭代都在查看相同的地址,因此它们都打印相同的值。
如您所参考的书中所述,处理此类情况的正确方法是在循环中定义一个新变量,将迭代值复制到该变量,然后将其传递给匿名函数,如下所示:
var rmdirs []func()
tempDirs := []string{"one", "two", "three", "four"}
for _, d := range tempDirs {
dir := d
fmt.Printf("dir=%v, *dir=%p\n", dir, &dir)
rmdirs = append(rmdirs, func() {
fmt.Printf("dir=%v, *dir=%p\n", dir, &dir)
})
}
fmt.Println("---")
for _, f := range rmdirs {
f()
}
Run Code Online (Sandbox Code Playgroud)
这会产生您期望的输出:
dir=one, *dir=0x40e128
dir=two, *dir=0x40e150
dir=three, *dir=0x40e168
dir=four, *dir=0x40e180
---
dir=one, *dir=0x40e128
dir=two, *dir=0x40e150
dir=three, *dir=0x40e168
dir=four, *dir=0x40e180
Run Code Online (Sandbox Code Playgroud)
游乐场链接:https : //play.golang.org/p/Ao6fC9i2DsG