### 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本身導致的,而是因爲它們都會等待循環結束後,再執行函數值。