go语言中匿名函数的作用域陷阱详解
众所周知在go语言中函数也可以当作变量在程序中使用,我们可以使用函数字面量在任何表达式内指定函数变量。但是在编写代码的时候请注意:如果一个函数在使用不是在该函数内部定义的变量时,这个变量的生命周期不是由其作用域决定的!
这段话什么意思呢,借鉴go语言圣经中的一段代码举个例子:
func square() func() int { //返回一个自己定义的函数类型 var x int = 0 return func() int { x++ return x } } func main() { f := square() fmt.Println(f()) // 1 fmt.Println(f()) // 2 fmt.Println(f()) // 3 fmt.Println(f()) // 4 }
在这段程序中,函数square返回了一个类型为func() int的函数类型,但在每次调用f()的时候,匿名函数内部都会在x原有的值的基础上+1来更新x的值,而不是把x值赋值为0,所有这段代码最终的执行结果为1 2 3 4。
这表明里层的匿名函数能获取和更新外层的square函数的局部变量,变量x在main函数中返回square函数后依然存在。我们可以将其类比为全局变量和函数的关系来理解:把square函数看作一个独立的go程序,那么局部变量x可视为在square函数内部的全局变量,显然匿名函数每次操作的都是同一个变量。我们加上变量的地址输出验证是否正确:
func square() func() int { //返回一个自己定义的函数类型 var x int = 0 return func() int { x++ fmt.Println(&x) return x } } func main() { f := square() fmt.Println(f()) fmt.Println(f()) fmt.Println(f()) fmt.Println(f()) }
结果:
可见调用的变量x的确是同一个变量,那么我们在捕获迭代变量时使用匿名函数的时候就需要小心,看下面这段程序:
func square(x int) int { fmt.Println(x) return x } func main() { var handlers []func() for i := 0; i < 10; i++ { handlers = append(handlers, func() { square(i) }) } for _, handler := range handlers { handler() } }
在这段程序中,变量i在for循环引进的一个块作用域进行声明,根据前面的经验我们可以知道:在循环里创建的所有函数变量共享相同的变量,i的值在迭代中不断更新,因此i的实际取值是循环中i的最后一个取值,并且所有的square函数都在处理同一个值,运行结果也可以证明这一点:
我们经常引入一个内部变量来解决这个问题,声明一个副本,并将这个副本的值传入而不是直接把i的值传入:
func square(x int) int { fmt.Println(x) return x * x } func main() { var handlers []func() for i := 0; i < 10; i++ { j := i handlers = append(handlers, func() { square(j) }) } for _, handler := range handlers { handler() } }
这样就得到了我们想要的运行结果:
总结
上一篇:浅谈Go1.18中的泛型编程
栏 目:Golang
下一篇:GO语言对数组切片去重的实现
本文标题:go语言中匿名函数的作用域陷阱详解
本文地址:http://www.codeinn.net/misctech/208440.html