Golang延迟行为

时间:2014-07-13 06:20:35

标签: go

Effective Go 陈述以下关于推迟:

  

延迟函数的参数(如果函数是方法,包括接收器)在延迟执行时评估,而不是在调用执行时评估。除了避免担心变量在函数执行时更改值,这意味着单个延迟调用站点可以推迟多个函数执行。这是一个愚蠢的例子。

for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}
     

延迟函数以LIFO顺序执行,因此当函数返回时,此代码将导致4 3 2 1 0打印。

这个例子让我很困惑。如果在执行延迟调用时评估参数,则for循环中的延迟应该打印5 5 5 5 5,因为当for循环结束时将调用defers,并且此时i将为5。因此,在for循环结束时评估延迟将导致所有呼叫都为5。

我在这里错过了什么吗?

3 个答案:

答案 0 :(得分:42)

这似乎是连贯的(参见&#34; Defer, Panic, and Recover&#34;)

在周围函数返回后,以后进先出顺序执行延迟函数调用。

此功能打印&#34; 3210&#34;:

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}

评估defer时的最后一次调用意味着i=3,前一个调用意味着i=2,依此类推。

Golang spec

  

每次&#34; defer&#34;语句执行时,调用的函数值和参数将照常计算并重新保存,但不执行实际的函数体。


  

当func结束时将调用defers

是的,但是在循环运行之前,它们的参数会被评估。

你在&#34; How golang's “defer” capture closure's parameter?&#34;中有一个更棘手的推迟案例。当与闭包(function literal)一起使用时,详见&#34; Why add “()” after closure body in Golang?&#34;。

答案 1 :(得分:13)

再往下一点,spec也明确表示在defer语句运行时评估参数,而不是在实际调用延迟函数时的返回/恐慌时间:

  

每次&#34;推迟&#34;语句执行时,调用的函数值和参数将照常计算并重新保存,但不执行实际的函数体。

是的,一次评估参数并且函数体在另一个上运行肯定会让人感到困惑。我被它抓住了。

答案 2 :(得分:4)

我认为你的困惑是关于“延迟执行”和“呼叫执行”这两个词的意思。我相信,“延迟执行”是指控制流到达以defer开始的行,这在循环内发生了五次。相反,“调用执行”是指执行fmt.Printf("%d ", i)时,当周围函数返回时。

如果这种解释是正确的,那么你的语句“因为当for循环结束时将调用deferred”是错误的(在循环之后将调用printf,但在内部调用defer) ,一切都与其他答案中解释的行为一致。