匿名函数 与 闭包

96 阅读5分钟

1.学习闭包先了解什么是匿名函数

package main

import "fmt"

// Golang的函数只能返回匿名函数!
// fun(int) 相当于 f 的类型
// f 是一个函数型变量
var f = func(int) {}
func main() {
   // 可以看到:f在这里使用的逻辑是简单的打印本身
   f = func(i int) { fmt.Println(i) }
   f(2) //2

   // 可以看到:f在这里的逻辑变成了打印本身的三次方
   f = func(i int) { fmt.Println(i * i * i) }
   f(2) //8
}

2.什么是闭包,闭包函数

闭包是匿名函数与匿名函数外部将使用变量的组合
所以:匿名函数 与 变量n 就可以称为是一个【闭包】
对应:main()函数就是一个【闭包函数】

func main() {
   
   //易错理解:f:= 2
   //习惯性思维:看到有返回值的函数,就立马算出返回值,扔出去就是
   //习惯性思维在这里的两个错误:
   //1.f的右边是函数调用,那才应该是把返回值扔出去,这里的f右边分明是一个匿名函数的定义
   //2.匿名函数返回的int是没错,但f得到的也不是匿名函数返回的值呀,(调用函数才看返回值),这里是得到的匿名函数定义
   //综上:f是一个需要先计算两步【加1,赋值】的函数
   n := 1
   f := func() int {
      //按道理来说,函数想要用函数外的变量,那么就只有一种情况,这个变量是【全局变量】
      //这里的n本质上来说是一个main的局部变量,但是由于【匿名函数的特殊性】,可以嵌套在普通函数的里面,所以n在【匿名函数】的角度就成了全局变量
      //因为n是在【匿名函数】外面的,所以匿名函数在这里用它认为的全局n是不报错未定义的
      n = n + 1
      return n
   }
   // 3次调用的 n 都是n:=1 的那个n
   // 因为 n 的范围是包含住 f 的
   fmt.Println(f()) // 2 别忘记括号,不加括号相当于 f 函数的地址
   fmt.Println(f()) // 3 别忘记括号,不加括号相当于 f 函数的地址
   fmt.Println(f()) // 4 别忘记括号,不加括号相当于 f 函数的地址
}

3.匿名函数调用 与 闭包函数的调用

区别匿名函数 与 闭包函数的调用

package main

import "fmt"

func f1(x int) func() int {
   return func() int {
      x += 10
      return x
   }
}

func main() {
   //---------------------------------------1
   x := 0
   f := func() {
      x += 1
   }
   f()//匿名函数调用
   fmt.Println(x) // 1

   //----------------------------------------2
   y := 10
   f1(y)()//闭包函数调用
   //【闭包函数】一个括号表示返回【匿名函数】,二个括号表示【返回值】
   //f1的第二个括号(形成了新的局部函数作用域),所以这个地方肯定涉及到了【值传递】或【指针传递】
   fmt.Println(y) //10

}

3.1匿名函数调用

func main() {

   var funcSlice []func()
   for i := 0; i <= 2; i++ {
      funcSlice = append(funcSlice, func() { println(i) })
      //易错:习惯性看见函数就计算
      //第一轮循环完就认为[func(){println(0)}]----i=1
      //第二轮循环完就认为[func(){println(0)},func(){println(1)}]----i=2
      //第三轮循环完就认为[func(){println(0)},func(){println(1)},func(){println(2)}]----i=3
      //所以:认为调用[0]结果是0
      //        调用[1]结果是1
      //函数计算是在调用的时候才进行,这里 append 只不过是进行了一个(匿名函数)切片的追加,根本没有调用

      //正解:
      //第一轮循环完就认为[func(){println(i)}]----i=1
      //第二轮循环完就认为[func(){println(i)},func(){println(i)}]----i=2
      //第三轮循环完就认为[func(){println(i)},func(){println(i)},func(){println(i)}]----i=3
      //所以:应该调用[0]结果是3
      //        调用[1]结果是3
      //函数内的执行是在调用的时候才进行,这里 append 只不过是进行了一个(匿名函数)切片的追加,根本没有调用

   }
   for j := 0; j <= 1; j++ {
      funcSlice[j]() //匿名函数调用
   }

}

3.2匿名函数调用进阶

func main() {

   var funcSlice []func()
   for i := 0; i <= 2; i++ {
      //将每一次的 i数据先存到当前循环轮次的局部变量 x 里面
      //即可实现打印出:0 1 .....
      //因为:每一轮都有一个新定义的x
      x := i//利用了匿名函数使用变量先找局部变量,再找全局变量的顺序特点
      funcSlice = append(funcSlice, func() { println(x) })
   }
   for j := 0; j <= 1; j++ {
      funcSlice[j]()//匿名函数调用
   }

}

3.3闭包函数调用进阶

package main

import "fmt"

func add() func() int {

   // x 和 return 出来的匿名函数共同形成闭包
   var x int
   // x 是 add 函数里面的 局部变量
   return func() int {
      x++
      return x
   }
}

func main() {
   i := add()
   //add表示闭包函数,add()表示匿名函数,add()()表示调用闭包函数即可得到x
   //i 被赋值为闭包函数,利用匿名函数的(特性)保存了x的状态
   fmt.Println(i()) // 1
   fmt.Println(i()) // 2
   //正常来讲:第二次调用闭包函数的时候,x开始不可能是1
   //因为:第一次调用的作用域和第二次调用的作用域是不同的
   //所以:第二次调用的的 x开始只能是0
   //但是:i 是一个闭包函数,(闭包函数可以实现:作用域的延续)
   //因为变量x在变量i里面,而i变量对于后面两行fmt打印都是全局变量,所以自然就实现了作用域延续

   //所以:x 在第二行fme计算的开始应该是 1

   fmt.Println(add()()) //1
   fmt.Println(add()()) //1
   //这里并没有使用中间的闭包函数变量存x状态
   //是直接使用的定义里面的原始闭包函数
   //所以:x 无法实现作用域的转移

   //综上:x 的生命周期没有随着作用域的结束而结束,而是跟着中间的闭包函数变量延续了,这种现象做 x 的逃逸,

   return
}