登录
首页 >  Golang >  Go问答

难以理解异常:迭代变量的捕获注意事项

来源:stackoverflow

时间:2024-03-24 21:27:40 235浏览 收藏

在 Go 中,循环变量在每次迭代中都会被重用,这意味着它们在匿名函数中捕获的值可能与循环创建时的值不同。这会导致异常行为,如在示例代码中所示,所有匿名函数都指向循环中最后一次迭代的值。解决方法是复制迭代值到一个新变量,然后在匿名函数中使用该变量。

问题内容

我正在学习 go,但无法理解

var rmdirs []func()

 for _, dir := range tempDirs() {
     os.MkdirAll(dir, 0755)
     rmdirs = append(rmdirs, func() {
         os.RemoveAll(dir) // NOTE: incorrect!
     })
 }

书上的解释我看了好几遍了,还是不明白为什么不对。

我记得在go中参数是按值传递的,所以每个循环dir都是不同的值,为什么不正确?


解决方案


您的直觉是正确的:go reuses the same address for the iteration values,因此不能保证当附加到 rmdirs 的匿名函数时 dir 所指向的值与创建该函数且首次捕获 dir 时的值相同。 The exact wording in the specs 是:

迭代变量可以通过“range”子句使用短变量声明(:=)的形式来声明。在这种情况下,它们的类型设置为各自迭代值的类型,并且它们的范围是“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()
}

运行时,会产生以下输出:

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

演示链接:https://play.golang.org/p/_rS8Eq9qShM

正如您所看到的,循环中的每次迭代都会重用相同的地址。匿名函数的每次迭代都查看相同的地址,因此它们都打印相同的值。

正如您引用的书中提到的,处理此类情况的正确方法是在循环中定义一个新变量,将迭代值复制到该变量,然后将其传递给匿名函数,如下所示: p>

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()
}

这会产生您期望的输出:

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

演示链接:https://play.golang.org/p/Ao6fC9i2DsG

本篇关于《难以理解异常:迭代变量的捕获注意事项》的介绍就到此结束啦,但是学无止境,想要了解学习更多关于Golang的相关知识,请关注golang学习网公众号!

声明:本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>