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。
我在这里错过了什么吗?
答案 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
,依此类推。
每次&#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
) ,一切都与其他答案中解释的行为一致。