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语言匿名函数作用域内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • go语言匿名函数的使用

    package main import ( "fmt" "go_code/chapter02/funinit/utils" ) // 3.全局匿名函数 var( Fun1 = func(n1 int,n2 int) int { return n1 * n2 } ) // init 函数,通常在init函数中完成初始化工作 func main(){ // 1.在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次 res1 := func(n1 int,n2 int

  • 秒懂Golang匿名函数

    上篇文章给大家介绍了go语言匿名函数的使用 需要的朋友点击查看.今天给大家介绍Golang匿名函数的相关知识,具体内容如下: 概念 所谓匿名函数,就是没有名字的函数 匿名函数(英语:Anonymous Function)在计算机编程中是指一类无需定义标识符(函数名)的函数或子程序,普遍存在于多种编程语言中.---wikipedia Golang是支持匿名函数的,即在需要使用函数时,再定义函数,匿名函数没有函数名,只有函数体,函数可以被作为一种类型被赋值给函数类型的变量,匿名函数往往以变量方式被传

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

    众所周知在go语言中函数也可以当作变量在程序中使用,我们可以使用函数字面量在任何表达式内指定函数变量.但是在编写代码的时候请注意:如果一个函数在使用不是在该函数内部定义的变量时,这个变量的生命周期不是由其作用域决定的! 这段话什么意思呢,借鉴go语言圣经中的一段代码举个例子: func square() func() int { //返回一个自己定义的函数类型 var x int = 0 return func() int { x++ return x } } func main() { f :

  • PHP中的函数声明与使用详解

      函数 1.  函数名是标识符之一,只能有字母数字下划线,开头不能是数字: 函数名的命名,必须符合"小驼峰法则"FUNC(),func(),Func(); 函数名不区分大小写; 函数名不能与已有函数同名,不能与内置函数名同名: 2.   function_exists("func");用于检测函数是否已经声明: 注意传入的函数名,必须是字符串格式,返回结果为true/false: echo打印时,true为1,false不显示:               [ph

  • C++ 中友元函数与友元类详解

    C++ 中友元函数与友元类详解 总的来说,友元分为两类:友元函数与友元类.友元是针对类而言,它提供了一种非类的成员函数来访问类的非公有成员的一种机制.可以把一个函数指定为某类的友元,这个函数称为这个类的友元函数.也可以将类A指定为类B的友元,则类A是类B的友元类,类A的所有成员函数均是类B的友元函数,均可以访问类B的非公有成员.        友元函数的注意事项: (1)友元函数不是类的成员函数,在函数体中访问对象的成员,必须用"对象名.对象成员"方式来访问, 友元函数可以访问类中的所

  • Go语言中的字符串处理方法示例详解

    1 概述 字符串,string,一串固定长度的字符连接起来的字符集合.Go语言的字符串是使用UTF-8编码的.UTF-8是Unicode的实现方式之一. Go语言原生支持字符串.使用双引号("")或反引号(``)定义. 双引号:"", 用于单行字符串. 反引号:``,用于定义多行字符串,内部会原样解析. 示例: // 单行 "心有猛虎,细嗅蔷薇" // 多行 ` 大风歌 大风起兮云飞扬. 威加海内兮归故乡. 安得猛士兮守四方! ` 字符串支持转义

  • 基于Python中求和函数sum的用法详解

    基于Python中求和函数sum的用法详解 今天在看<集体编程智慧>这本书的时候,看到一段Python代码,当时是百思不得其解,总觉得是书中排版出错了,后来去了解了一下sum的用法,看了一些Python大神写的代码后才发现是自己浅薄了!特在此记录一下.书中代码段摘录如下: from math import sqrt def sim_distance(prefs, person1, person2): # 得到shared_items的列表 si = {} for item in prefs[p

  • C语言中关于动态内存分配的详解

    目录 一.malloc 与free函数 二.calloc 三.realloc 四.常见的动态内存的错误 [C语言]动态内存分配 本期,我们将讲解malloc.calloc.realloc以及free函数. 这是个动态内存分配函数的头文件都是 <stdlib.h>. c语言中动态分配内存的函数,可能有些初学c语言的人不免要问了:我们为什么要通过函数来实现动态分配内存呢? 首先让我们熟悉一下计算机的内存吧!在计算机的系统中大致有这四个内存区域: 1)栈:在栈里面储存一些我们定义的局部变量以及形参(

  • C++函数对象Functor与匿名函数对象Lambda表达式详解

    目录 1函数对象Functor(仿函数) 1.1概念 1.2代码实例 1.3调用效率 2.匿名函数对象Lambda表达式 2.1使用形式 2.2代码实例 3总结 1函数对象Functor(仿函数) 1.1概念 函数对象就是类对象,生成这个类对象的类中,拥有一个小括号运算符重载函数. 重载了小括号运算符的类的类对象,就叫函数对象. 1.2代码实例 #include <iostream> using namespace std; template <class T1> class A

  • C语言中进程间通讯的方式详解

    目录 一.无名管道 1.1无名管道的原理 1.2功能 1.3无名管道通信特点 1.4无名管道的实例 二.有名管道 2.1有名管道的原理 2.2有名管道的特点 2.3有名管道实例 三.信号 3.1信号的概念 3.2发送信号的函数 3.3常用的信号 3.4实例 四.IPC进程间通信 4.1IPC进程间通信的种类 4.2查看IPC进程间通信的命令 4.3消息队列 4.4共享内存 4.5信号灯集合 一.无名管道 1.1无名管道的原理 无名管道只能用于亲缘间进程的通信,无名管道的大小是64K.无名管道是内

  • Python pandas中apply函数简介以及用法详解

    目录 1.基本信息 2.语法结构 3.使用案例 3.1 DataFrame使用apply 3.2 Series使用apply 3.3 其他案例 4.总结 参考链接: 1.基本信息 ​ Pandas 的 apply() 方法是用来调用一个函数(Python method),让此函数对数据对象进行批量处理.Pandas 的很多对象都可以使用 apply() 来调用函数,如 Dataframe.Series.分组对象.各种时间序列等. 2.语法结构 ​ apply() 使用时,通常放入一个 lambd

  • C语言中文件常见操作的示例详解

    目录 文件打开和关闭 文件写入 文件读取 fseek函数 ftell函数 Demo示例 解决读取乱码 FILE为C语言提供的文件类型,它是一个结构体类型,用于存放文件的相关信息.文件打开成功时,对它作了内存分配和初始化. 每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节. 一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便. 文件打开和关闭 C语言的安全文件打开函数为_wfopen_s和_fopen_s

随机推荐