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

huangapple go评论87阅读模式
英文:

How golang's "defer" capture closure's parameter?

问题

这是我的代码(运行):

package main

import "fmt"

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

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

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

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

输出:

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

问题:部分 2 和部分 3 有什么区别?为什么部分 2 输出的是 "44444" 而不是 "43210"?

英文:

Here is my code (run):

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
}

Output:

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

Question: What's the difference between part 2 & part 3? Why part 2 output "44444" instead of "43210"?

答案1

得分: 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语句执行时,表达式'e'在defer语句执行时会被评估。

英文:

The 'part 2' closure captures the variable 'i'. When the code in the closure (later) executes, the variable 'i' has the value which it had in the last iteration of the range statement, ie. '4'. Hence the

4 4 4 4 4

part of the output.

The 'part 3' doesn't capture any outer variables in its closure. As the specs say:

> Each time the "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked.

So each of the defered function calls has a different value of the 'n' parameter. It is the value of the 'i' variable in the moment the defer statement was executed. Hence the

4 3 2 1 0

part of the output because:

> ... deferred calls are executed in LIFO order immediately before the surrounding function returns ...


The key point to note is that the 'f()' in 'defer f()' is not executed when the defer statement executes

but

the expression 'e' in 'defer f(e)' is evaluated when the defer statement executes.

答案2

得分: 1

我想举一个例子来改进对defer机制的理解,首先按照原样运行这段代码,然后交换标记为(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()  // 语句(A)
	c.val = 2 // 语句(B)
}

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

英文:

I would like to address another example in order to improve the understanding of defer mechanish, run this snippet as it is first, then switch order of the statements marked as (A) and (B), and see the result to yourself.

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)
}

I keep wonderng what are the correct keywords or concepts to apply here. It looks like that the expression c.method is evaluated, thus returning a function binded to the actual state of the component "c" (like taking an snapshot of the component's internal state).
I guess the answer involves not only defer mechanish also how funtions with value or pointer receiver works. Do note that it also happens that if you change the func named method to be a pointer receiver the defer prints c.val as 2, not as 0.

huangapple
  • 本文由 发表于 2013年4月15日 16:24:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/16010694.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定