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
}