go语言中匿名函数的作用域陷阱详解

 更新时间:2022年05月06日 10:52:02   作者:Timo  
GO语言的匿名函数(anonymous function),其实就是闭包.是指不需要定义函数名的一种函数实现方式,下面这篇文章主要给大家介绍了关于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()
   }
}

这样就得到了我们想要的运行结果:

总结

到此这篇关于go语言中匿名函数作用域陷阱的文章就介绍到这了,更多相关go语言匿名函数作用域内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解go-zero如何使用validator进行参数校验

    详解go-zero如何使用validator进行参数校验

    这篇文章主要介绍了如何使用validator库做参数校验的一些十分实用的使用技巧,包括翻译校验错误提示信息、自定义提示信息的字段名称、自定义校验方法等,感兴趣的可以了解下
    2024-01-01
  • Golang的select多路复用及channel使用操作

    Golang的select多路复用及channel使用操作

    这篇文章主要介绍了Golang的select多路复用及channel使用操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • go 压缩解压zip文件源码示例

    go 压缩解压zip文件源码示例

    这篇文章主要为大家介绍了go压缩及解压zip文件的源码示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • Go语句与表达式案例手册深度解析

    Go语句与表达式案例手册深度解析

    这篇文章主要为大家介绍了Go语句与表达式案例手册深度解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • go-zero数据的流处理利器fx使用详解

    go-zero数据的流处理利器fx使用详解

    这篇文章主要为大家介绍了go-zero数据的流处理利器fx使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • golang中import cycle not allowed解决的一种思路

    golang中import cycle not allowed解决的一种思路

    这篇文章主要给大家介绍了关于golang中import cycle not allowed解决的一种思路,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧
    2018-08-08
  • go语言使用Chromedp实现二维码登陆教程示例源码

    go语言使用Chromedp实现二维码登陆教程示例源码

    这篇文章主要为大家介绍了go语言使用Chromedp实现二维码登陆示例源码,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-04-04
  • golang中protobuf的使用详解

    golang中protobuf的使用详解

    protobuf是Google公司提出的一种轻便高效的结构化数据存储格式,常用于结构化数据的序列化,具有语言无关、平台无关、可扩展性特性,常用于通讯协议、服务端数据交换场景,下面我们就来看看golang中protobuf的具体使用吧
    2023-10-10
  • Go语言构建流数据pipeline的示例详解

    Go语言构建流数据pipeline的示例详解

    Go的并发原语可以轻松构建流数据管道,从而高效利用 I/O 和多个 CPU, 本文展示了此类pipelines的示例,强调了操作失败时出现的细微之处,并介绍了干净地处理失败的技术,希望对大家有所帮助
    2024-02-02
  • Golang实现Biginteger大数计算实例详解

    Golang实现Biginteger大数计算实例详解

    这篇文章主要为大家介绍了Golang实现Biginteger大数计算实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07

最新评论