range 踩坑小记——为啥删不掉文件夹?
问题
最近在学 go ,自己做了一个文件夹创建之后再删除的练习,代码如下:
package main
import (
"fmt"
"os"
)
//建立三个临时文件夹 a b c
func tempDirs() []string {
return []string{"a", "b", "c"}
}
func main() {
//以队列的方式来存放删除操作
var rmdirs []func()
for _, dir := range tempDirs() {
os.MkdirAll(dir, 0755)
rmdirs = append(rmdirs, func() {
fmt.Println("准备删除文件夹", dir)
os.RemoveAll(dir)
})
}
//做点别的事情
for _, rm := range rmdirs {
rm()
}
}
表现
运行起来之后发现,只删除的文件夹 c,那么问题来了为啥不是按照预期依次删除 a b c 呢?
原因
这里不卖关子了,直接贴原因出来:
- 这里就是因为 range 循环的时候,只是拷贝了循环对象中的元素值出来,放到了临时变量当中,这个临时变量的地址是不变的,循环结束的时候,该 dir 临时变量存放的就是 c。
- 然后在我们 append 进匿名函数中的时候,这个 dir 变量实际上是把地址放到函数体内部,后续执行的时候就直接读取这个函数体内部变量的地址。因为该地址最后存放的值就是c,所以后面我们循环执行的时候删除的就是 c。
那么应该如何验证以上两条结论呢?
先验证原因1,这里直接在循环中打印 dir 的地址就好了,改写例程如下:
package main
import (
"fmt"
"os"
)
//建立三个临时文件夹 a b c
func tempDirs() []string {
return []string{"a", "b", "c"}
}
func main() {
//以队列的方式来存放删除操作
var rmdirs []func()
for _, dir := range tempDirs() {
//此处打印 dir 地址
fmt.Println("dir 的地址是 ",&dir)
os.MkdirAll(dir, 0755)
rmdirs = append(rmdirs, func() {
fmt.Println("准备删除文件夹", dir)
os.RemoveAll(dir)
})
}
//做点别的事情
for _, rm := range rmdirs {
rm()
}
}
运行之后可以看到类似如下输出:
dir 的地址是 0x10b44100 dir 的地址是 0x10b44100 dir 的地址是 0x10b44100 准备删除文件夹 c 准备删除文件夹 c 准备删除文件夹 c
所以 dir 的地址都是一样的。
接下来验证结论 2 ,这里要稍微有点变化,根据结论 2 可以推断:如果 dir 地址被存放进了匿名函数的内部,后续在匿名函数集 rmdirs 进行循环执行之前,我们去改变这个 dir 临时变量中存放的值(例如把 dir 内部存放的值改为 a ),这样一来应该就是删除 a 文件夹了。
那么应该如何去改变这个 dir 临时变量的值呢?dir 变量的作用域只作用在 range 这一块中,出了 range 之后,其他地方是访问不了的。
哈哈,你想到了,我们可以借助一个外部变量指针来做这件事,改写例程如下:
package main
import (
"fmt"
"os"
)
//建立三个临时文件夹
func tempDirs() []string {
return []string{"a", "b", "c"}
}
func main() {
//以队列的方式来存放删除操作
var rmdirs []func()
var globalDir *string
for _, dir := range tempDirs() {
fmt.Println("dir 的地址是 ", &dir)
//我们从这里获取 dir 的地址,存放到globalDir中
globalDir = &dir
os.MkdirAll(dir, 0755)
rmdirs = append(rmdirs, func() {
fmt.Println("准备删除文件夹", dir)
os.RemoveAll(dir)
})
}
//做点别的事情:这里我们把要删除的文件夹改成 a,
//也就是说如下操作会把 dir 临时变量的值改写成 a
*globalDir = "a"
for _, rm := range rmdirs {
rm()
}
}
运行之后,得到如下输出:
dir 的地址是 0x10a84100
dir 的地址是 0x10a84100
dir 的地址是 0x10a84100
准备删除文件夹 a
准备删除文件夹 a
准备删除文件夹 a
所以第 2 条结论也得到了证实。
解决
问题原因也找到了,那么如何解决呢,其实我们可以在range 内部加入临时变量解决这个问题,只需要一句就可以搞定:
package main
import (
"fmt"
"os"
)
//建立三个临时文件夹 a b c
func tempDirs() []string {
return []string{"a", "b", "c"}
}
func main() {
//以队列的方式来存放删除操作
var rmdirs []func()
for _, dir := range tempDirs() {
//将循环体内部的dir 再次赋值给一个新变量
//此时dir的地址就变化了,不信自己打印试试
dir := dir
os.MkdirAll(dir, 0755)
rmdirs = append(rmdirs, func() {
fmt.Println("准备删除文件夹", dir)
os.RemoveAll(dir)
})
}
//做点别的事情
for _, rm := range rmdirs {
rm()
}
}
以上就是全部踩坑小记录了,希望能对你有所帮助。Happy Coding!
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: