mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-01 10:38:59 +00:00
59 lines
2.5 KiB
Markdown
59 lines
2.5 KiB
Markdown
### 5.6.1. 警告:捕獲迭代變量
|
||
|
||
本節,將介紹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本身導致的,而是因爲它們都會等待循環結束後,再執行函數值。
|