mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-08-12 01:41:50 +00:00
ch5-6 done
This commit is contained in:
@@ -1,3 +1,58 @@
|
||||
### 5.6.1. 警告:捕獲迭代變量
|
||||
|
||||
TODO
|
||||
本节,将介绍Go词法作用域的一个陷阱。请务必仔细的阅读,弄清楚发生问题的原因。即使是经验丰富的程序员也会在这个问题上犯错误。
|
||||
|
||||
考虑这个样一个问题:你被要求首先创建一些目录,再将目录删除。在下面的例子中我们用函数值来完成删除操作。下面的示例代码需要引入os包。为了使代码简单,我们忽略了所有的异常处理。
|
||||
|
||||
```Go
|
||||
var rmdirs []func()
|
||||
for _, d := range tempDirs() {
|
||||
dir := d // NOTE: necessary!
|
||||
os.MkdirAll(dir, 0755) // creates parent directories too
|
||||
rmdirs = append(rmdirs, func() {
|
||||
os.RemoveAll(dir)
|
||||
})
|
||||
}
|
||||
// ...do some work…
|
||||
for _, rmdir := range rmdirs {
|
||||
rmdir() // clean up
|
||||
}
|
||||
```
|
||||
|
||||
你可能会感到困惑,为什么要在循环体中用循环变量d赋值一个新的局部变量,而不是像下面的代码一样直接使用循环变量dir。需要注意,下面的代码是错误的。
|
||||
|
||||
```go
|
||||
var rmdirs []func()
|
||||
for _, dir := range tempDirs() {
|
||||
os.MkdirAll(dir, 0755)
|
||||
rmdirs = append(rmdirs, func() {
|
||||
os.RemoveAll(dir) // NOTE: incorrect!
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
问题的原因在于循环变量的作用域。在上面的程序中,for循环语句引入了新的词法块,循环变量dir在这个词法块中被声明。在该循环中生成的所有函数值都共享相同的循环变量。需要注意,函数值中记录的是循环变量的内存地址,而不是循环变量某一时刻的值。以dir为例,后续的迭代会不断更新dir的值,当删除操作执行时,for循环已完成,dir中存储的值等于最后一次迭代的值。这意味着,每次对os.RemoveAll的调用删除的都是相同的目录。
|
||||
|
||||
通常,为了解决这个问题,我们会引入一个与循环变量同名的局部变量,作为循环变量的副本。比如下面的变量dir,虽然这看起来很奇怪,但却很有用。
|
||||
|
||||
```Go
|
||||
for _, dir := range tempDirs() {
|
||||
dir := dir // declares inner dir, initialized to outer dir
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
这个问题不仅存在基于range的循环,在下面的例子中,对循环变量i的使用也存在同样的问题:
|
||||
|
||||
```Go
|
||||
var rmdirs []func()
|
||||
dirs := tempDirs()
|
||||
for i := 0; i < len(dirs); i++ {
|
||||
os.MkdirAll(dirs[i], 0755) // OK
|
||||
rmdirs = append(rmdirs, func() {
|
||||
os.RemoveAll(dirs[i]) // NOTE: incorrect!
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
如果你使用go语句(第八章)或者defer语句(5.8节)会经常遇到此类问题。这不是go或defer本身导致的,而是因为它们都会等待循环结束后,再执行函数值。
|
||||
|
Reference in New Issue
Block a user