在Go语言中,闭包(closure)是指一个函数值(function value)捕获了其外部作用域中的变量。闭包在Go中非常常见,尤其是在使用匿名函数时。然而,闭包在使用时可能会遇到一个常见的陷阱:匿名函数中的变量值总是相同的。这个问题的根源在于闭包捕获的是变量的引用,而不是变量的值。
考虑以下代码:
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i)
}()
}
time.Sleep(time.Second)
}
你可能期望输出是:
0
1
2
但实际上,输出可能是:
3
3
3
在这个例子中,i
是一个在 for
循环中声明的变量。每次循环迭代时,i
的值都会递增。然而,匿名函数捕获的是 i
的引用,而不是 i
的当前值。因此,当匿名函数执行时,它们都会打印出 i
的最终值(即 3
),而不是在创建闭包时的值。
要解决这个问题,可以通过在每次循环迭代时创建一个新的局部变量来捕获 i
的值。这样,每个闭包都会捕获一个不同的变量,而不是共享同一个变量。
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 3; i++ {
i := i // 创建一个新的局部变量
go func() {
fmt.Println(i)
}()
}
time.Sleep(time.Second)
}
在这个修改后的代码中,i := i
创建了一个新的局部变量 i
,它的作用域仅限于当前循环迭代。因此,每个闭包都会捕获一个不同的 i
,输出将是:
0
1
2
你也可以将 i
作为参数传递给匿名函数:
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 3; i++ {
go func(i int) {
fmt.Println(i)
}(i)
}
time.Sleep(time.Second)
}
在这个版本中,i
作为参数传递给匿名函数,因此每个闭包都会捕获一个不同的 i
值。输出同样会是:
0
1
2
Go语言中的闭包捕获的是变量的引用,而不是变量的值。因此,在使用闭包时,特别是在循环中,需要注意变量的作用域和生命周期。通过创建新的局部变量或将变量作为参数传递给闭包,可以避免闭包陷阱,确保闭包捕获的是预期的值。