以下Go代码演示了defer和go闭包之间闭包捕获规则的区别。在一个教程中,我被告知for循环变量的范围仅限于循环体,但这里看起来有些不同。
package main
import "fmt"
func deferred() {
for i := 0; i < 5; i++ {
defer fmt.Println(i)
}
}
func cps() {
clo := new(func())
*clo = func() {}
defer func() { (*clo)() }()
for i := 0; i < 5; i++ {
oldClo := *clo
*clo = func() {
fmt.Println(i)
oldClo()
}
}
}
func cpsCpy() {
clo := new(func())
*clo = func() {}
defer func() { (*clo)() }()
for i := 0; i < 5; i++ {
oldClo := *clo
cpy := i
*clo = func() {
fmt.Println(cpy)
oldClo()
}
}
}
func main() {
fmt.Println("defer")
deferred()
fmt.Println("cps")
cps()
fmt.Println("cpsCpy")
cpsCpy()
}
这会产生输出:
defer
4
3
2
1
0
cps
5
5
5
5
5
cpsCpy
4
3
2
1
0
如果差异是故意的,那么有什么不同的用例可以证明它的合理性呢?
答案 0 :(得分:2)
spec for defer对此很清楚。对于那些关心变量捕获规则之类的东西而言,这个规范通常是至关重要的读物,并且相对较短。它在这里说的是:
每次执行“延迟”语句时,将像往常一样评估调用的函数值和参数,并重新保存但不调用实际函数。
the blog post on defer, panic, and recover也涵盖了这一点。
总是很难证明设计决策的合理性让每个人都感到满意,而defer
尤其让人偶尔感到惊讶,特别是当你defer f1(f2(x))
忘记f2(x)
时立即
可能有助于记住它的一件事是,defer
(或go
)之后的内容是一个函数调用,而只是函数调用本身(甚至连params的评估都没有及时改变。闭包定义了代码块,并在代码执行时访问变量。
由于您对使用闭包获得的变量捕获类型感到好奇,因此如果您使用类似ForEach
的方法的闭包,那么它就是一个有用的示例:
myBTree.ForEach(func (key, value []byte) {
//...
})
如果在常规for
循环中,大括号之间的代码可以修改外部作用域中的变量,那就太好了。这要求闭包访问变量,而不仅仅是它们在特定时间内的值。