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。
我在这里错过了什么吗?
这似乎是连贯的(另见“Defer, Panic, and Recover”)
在周围函数返回后,延迟函数调用以Last In First Out顺序执行。
此功能打印“3210”:
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}
评估defer
时的最后一次调用意味着i=3
,前一个调用意味着i=2
,依此类推。
每次执行“
defer
”语句时,调用的函数值和参数将照常计算并重新保存,但不执行实际的函数体。
当func结束时,将调用
defers
是的,但是他们的参数在循环运行之前进行了评估。
当你使用封闭(How golang's “defer” capture closure's parameter?)时,你在“function literal”中有一个更棘手的延迟案例,详见“Why add “()
” after closure body in Golang?”。
更进一步,spec还明确表示在defer
语句运行时评估参数,而不是在实际调用延迟函数时的返回/恐慌时间:
每次执行“延迟”语句时,将像往常一样评估调用的函数值和参数,并重新保存,但不执行实际的函数体。
是的,一次评估参数并且函数体在另一个上运行肯定会令人困惑。我被它抓住了。
我认为你的困惑是关于“延迟执行”和“呼叫执行”这两个词的含义。我相信,“延迟执行”是当控制流量达到以defer
开始的线时,即这在循环内发生了五次。相反,“调用执行”是在执行fmt.Printf("%d ", i)
时,即当周围函数返回时。
如果这种解释是正确的,那么你的语句“因为当for循环结束时将会调用defers”是错误的(printf
将在循环之后调用,但defer
在内部调用),并且所有内容都符合在其他答案。