一尘不染

Go中捕获的闭包(用于循环变量)

go

Go编译器不应该将for...range循环变量捕获为本地分配的闭包变量吗?

长版:

这也引起了我对C#的困惑,并且我试图理解它。这就是为什么它在C#5.0中已得到修复foreach(原因:循环变量
不能 在循环体内改变)以及未在C#for循环中对其进行修复的原因(原因:循环变量 可以 在循环体内改变)。

现在(对我而言)for...range,Go中的循环看起来很像foreachC#中的循环,但是尽管事实是我们无法更改这些变量(例如kvfor k, v := range m { ... })。仍然我们必须首先将它们复制到某些本地闭包中,以使其表现出预期的效果。

这背后的原因是什么?(我怀疑这是因为Go用for相同的方式对待任何循环;但我不确定)。

这是一些代码来检查描述的行为:

func main() {
    lab1() // captured closure is not what is expected
    fmt.Println(" ")

    lab2() // captured closure is not what is expected
    fmt.Println(" ")

    lab3() // captured closure behaves ok
    fmt.Println(" ")
}

func lab3() {
    m := make(map[int32]int32)
    var i int32
    for i = 1; i <= 10; i++ {
        m[i] = i
    }

    l := [](func() (int32, int32)){}
    for k, v := range m {
        kLocal, vLocal := k, v // (C) captures just the right values assigned to k and v
        l = append(l, func() (int32, int32) {
            return kLocal, vLocal
        })
    }

    for _, x := range l {
        k, v := x()
        fmt.Println(k, v)
    }
}

func lab2() {
    m := make(map[int32]int32)
    var i int32
    for i = 1; i <= 10; i++ {
        m[i] = i
    }

    l := [](func() (int32, int32)){}
    for k, v := range m {
        l = append(l, func() (int32, int32) {
            kLocal, vLocal := k, v // (B) captures just the last values assigned to k and v from the range
            return kLocal, vLocal
        })
    }

    for _, x := range l {
        k, v := x()
        fmt.Println(k, v)
    }
}

func lab1() {
    m := make(map[int32]int32)
    var i int32
    for i = 1; i <= 10; i++ {
        m[i] = i
    }

    l := [](func() (int32, int32)){}
    for k, v := range m {
        l = append(l, func() (int32, int32) { return k, v }) // (A) captures just the last values assigned to k and v from the range
    }

    for _, x := range l {
        k, v := x()
        fmt.Println(k, v)
    }
}

如图所示lab1,在注释中,// (A)我们仅从中获得了最后一个值range。输出就像打印9,9十次,而不是显示预期的结果,如1,1,,2,2…(当然,路线图不一定要在Go中排序,因此我们可能将3,3十次视为最后一对值;而不是将10,10十次视为最后一个值。对值)。在处的注释// (B)处的代码也是如此lab2,这是预期的,因为我们正在尝试捕获内部作用域内的外部变量(我也尝试这样做是为了放置它)。在lab3在在注释代码// (C)一切正常,你会十岁了数对有喜欢的1,12,2....

我试图用 闭包+函数 代替Go中的 tuple


阅读 235

收藏
2020-07-02

共1个答案

一尘不染

您是否希望对变量或值进行闭包?例如,

package main

import "fmt"

func VariableLoop() {
    f := make([]func(), 3)
    for i := 0; i < 3; i++ {
        // closure over variable i
        f[i] = func() {
            fmt.Println(i)
        }
    }
    fmt.Println("VariableLoop")
    for _, f := range f {
        f()
    }
}

func ValueLoop() {
    f := make([]func(), 3)
    for i := 0; i < 3; i++ {
        i := i
        // closure over value of i
        f[i] = func() {
            fmt.Println(i)
        }
    }
    fmt.Println("ValueLoop")
    for _, f := range f {
        f()
    }
}

func VariableRange() {
    f := make([]func(), 3)
    for i := range f {
        // closure over variable i
        f[i] = func() {
            fmt.Println(i)
        }
    }
    fmt.Println("VariableRange")
    for _, f := range f {
        f()
    }
}

func ValueRange() {
    f := make([]func(), 3)
    for i := range f {
        i := i
        // closure over value of i
        f[i] = func() {
            fmt.Println(i)
        }
    }
    fmt.Println("ValueRange")
    for _, f := range f {
        f()
    }
}

func main() {
    VariableLoop()
    ValueLoop()
    VariableRange()
    ValueRange()
}

输出:

可变回路
3
3
3
价值循环
0
1个
2
可变范围
2
2
2
价值范围
0
1个
2

参考文献:

Go编程语言规范

函数文字

函数文字是闭包:它们可以引用周围函数中定义的变量。然后,这些变量在周围的函数和函数文字之间共享,并且只要可以访问它们就可以保留。

常见问题解答:以goroutines身份运行闭包会发生什么?

要将v的当前值绑定到每个闭包启动时,必须修改内部循环以在每次迭代时创建一个新变量。一种方法是将变量作为参数传递给闭包。

甚至更容易的是使用声明样式创建一个新变量,该声明样式可能看起来很奇怪,但在Go中可以正常工作。

2020-07-02