面试题答案
一键面试可能出现的陷阱
当闭包引用函数参数时,由于闭包会捕获外部变量的引用,而非值。如果外部变量在闭包执行前发生变化,闭包使用的值可能并非预期。例如在循环中创建闭包并引用循环变量,多个闭包可能会共享同一个循环变量,导致最终执行闭包时得到的结果并非期望的不同值。
代码复现陷阱
package main
import (
"fmt"
)
func createClosures() []func() {
var funcs []func()
for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i)
})
}
return funcs
}
在上述代码中,createClosures
函数创建了一个闭包切片。每个闭包都引用了循环变量 i
。当这些闭包稍后被调用时:
func main() {
funcs := createClosures()
for _, f := range funcs {
f()
}
}
输出结果会是 3 3 3
,而不是期望的 0 1 2
。这是因为所有闭包共享了同一个 i
,当闭包执行时,i
的值已经变为 3
。
规避策略
- 传值给闭包:在创建闭包时,将循环变量的值作为参数传递给闭包,这样每个闭包会有自己独立的变量副本。
func createClosuresFixed() []func() {
var funcs []func()
for i := 0; i < 3; i++ {
index := i
funcs = append(funcs, func() {
fmt.Println(index)
})
}
return funcs
}
这里通过 index := i
创建了一个新的局部变量 index
,每个闭包捕获的是 index
的值,而不是 i
的引用。
2. 直接传参:在创建闭包时直接将循环变量作为参数传递进去。
func createClosuresDirect() []func() {
var funcs []func()
for i := 0; i < 3; i++ {
funcs = append(funcs, func(j int) func() {
return func() {
fmt.Println(j)
}
}(i))
}
return funcs
}
这种方式同样为每个闭包提供了独立的变量值,在 main
函数中调用这些闭包时,将得到期望的 0 1 2
输出。