Golang中的"defer"如何捕获闭包参数?

39

这是我的代码(运行):

package main

import "fmt"

func main() {
    var whatever [5]struct{}

    for i := range whatever {
        fmt.Println(i)
    } // part 1

    for i := range whatever {
        defer func() { fmt.Println(i) }()
    } // part 2

    for i := range whatever {
        defer func(n int) { fmt.Println(n) }(i)
    } // part 3
}

输出:

0 1 2 3 4 4 3 2 1 0 4 4 4 4 4

问题:第二部分和第三部分有什么不同?为什么第二部分输出的是“44444”而不是“43210”?

2个回答

49

在“part 2”闭包中,捕获了变量“i”。当闭包中的代码(稍后)执行时,“i”变量具有range语句的最后一次迭代中的值,即“4”。因此

4 4 4 4 4

输出的一部分。

在其闭包中,“part 3”未捕获任何外部变量。正如规范所述:

  

每次执行“defer”语句时,函数值和调用的参数会像往常一样进行评估并保存,但实际的函数不会被调用。

因此,每个被延迟的函数调用都具有不同的“n”参数值。它是在执行 defer 语句时的“i”变量的值。因此,

4 3 2 1 0

部分输出的原因是:

......延迟调用在包围函数返回之前以LIFO顺序执行......


需要注意的关键点是,在defer语句执行时,'defer f()'中的'f()'不会被执行。

但是

在defer语句执行时,'defer f(e)'中的表达式'e' 会被评估


1

我想举另一个例子来提高对延迟机制的理解,首先按原样运行此片段,然后切换标记为(A)和(B)的语句的顺序,并观察结果。

package main

import (
    "fmt"
)

type Component struct {
    val int
}

func (c Component) method() {
    fmt.Println(c.val)
}

func main() {
    c := Component{}
    defer c.method()  // statement (A)
    c.val = 2 // statement (B)
}

我一直在思考应该使用哪些正确的关键字或概念。看起来表达式c.method被评估,因此返回一个绑定到组件"c"实际状态的函数(就像拍摄组件内部状态的快照)。我猜答案不仅涉及到defer机制,还涉及到带值或指针接收器的函数如何工作。请注意,如果将名为method的函数更改为指针接收器,则defer会将c.val打印为2而不是0。

也许这是类似于以下示例的复杂示例 https://play.golang.org/p/yNma_82QIJ9 - Victor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接