闭包
简介
作用:缩小变量作用域,减少对全局变量的污染
闭包又是什么?你可以想象一下,在一个函数中存在对外来标识符的引用。所谓的外来标识符,既不代表当前函数的任何参数或结果,也不是函数内部声明的,它是直接从外边拿过来的
一个函数捕获了和他在同一个作用域的其他常量和变量.这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包能够使用这些常量或者变量.
它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用他,这些变量就还会存在.
在go里面,所有的匿名函数都是闭包
func main() {
a := 10
str := "mike"
//匿名函数,没有函数名字,函数定义没有调用
f1 := func() {
fmt.Println("a = ", a)
fmt.Println("str = ", str)
}
//调用
f1()
//给一个函数类型起别名
type FuncType func() //函数没有参数没有返回值
//声明变量
var f2 FuncType
f2 = f1
f2()
//定义匿名函数,同时调用
func() {
fmt.Printf("a = %d, str = %s\n", a, str)
}() //后面的()代表调用此匿名函数
//带参数的匿名函数
f3 := func(i, j int) {
fmt.Printf("a = %d, str = %s\n", a, str)
}
f3(1, 2)
//有参数有返回值
x, y := func(i, j int) (max, min int) {
if i > j {
max = i
min = j
} else {
max = j
min = i
}
return
}(10, 20)
fmt.Print(x, y)
}
闭包以引用的方式捕获外部变量
func main() {
a := 10
str := "nike"
func() {
a = 666
str = "go"
fmt.Printf("内部: a = %d, str = %s\n", a, str)
}() //()代表直接调用
fmt.Printf("外部: a = %d, str =%s\n", a, str)
}
输出
内部: a = 666, str = go
外部: a = 666, str =go
闭包保存变量
func test02() func() int {
var x int //没有初始化,值为0
return func() int {
x++
return x * x
}
}
func main() {
//返回函数类型
f := test02()
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
}
使用
Go 函数可以是一个闭包。闭包是一个函数值,它引用了函数体之外的变量。 这个函数可以对这个引用的变量进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。
例如,函数 adder 返回一个闭包。每个返回的闭包都被绑定到其各自的 sum 变量上。
在上面例子中(这里重新贴下代码,和上面代码一样):
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
如pos := adder()的adder()表示返回了一个闭包,并赋值给了pos,同时,这个被赋值给了pos的闭包函数被绑定在sum变量上,因此pos闭包函数里的变量sum和neg变量里的sum毫无关系。
func adder() func(int) int
的func(int) int
表示adder()的输出值的类型是func(int) int这样一个函数
没有闭包的时候,函数就是一次性买卖,函数执行完毕后就无法再更改函数中变量的值(应该是内存释放了);有了闭包后函数就成为了一个变量的值,只要变量没被释放,函数就会一直处于存活并独享的状态,因此可以后期更改函数中变量的值(因为这样就不会被go给回收内存了,会一直缓存在那里)。
比如,实现一个计算功能:一个数从0开始,每次加上自己的值和当前循环次数(当前第几次,循环从0开始,到9,共10次),然后*2,这样迭代10次:
没有闭包的时候这么写:
func abc(x int) int {
return x * 2
}
func main() {
var a int
for i := 0; i < 10; i ++ {
a = abc(a+i)
fmt.Println(a)
}
}
如果用闭包可以这么写:
func abc() func(int) int {
res := 0
return func(x int) int {
res = (res + x) * 2
return res
}
}
func main() {
a := abc()
for i := 0; i < 10; i++ {
fmt.Println(a(i))
}
}
2种写法输出值都是:
0
2
8
22
52
114
240
494
1004
2026
从上面例子可以看出闭包的3个好处:
-
不是一次性消费,被引用声明后可以重复调用,同时变量又只限定在函数里,同时每次调用不是从初始值开始(函数里长期存储变量)
这有点像使用面向对象的感觉,实例化一个类,这样这个类里的所有方法、属性都是为某个人私有独享的。但比面向对象更加的轻量化
-
用了闭包后,主函数就变得简单了,把算法封装在一个函数里,使得主函数省略了a=abc(a+i)这种麻烦事了
-
变量污染少,因为如果没用闭包,就会为了传递值到函数里,而在函数外部声明变量,但这样声明的变量又会被下面的其他函数或代码误改。
关于闭包的第一个好处,再啰嗦举个例子
- 若不用闭包,则容易对函数外的变量误操作(误操作别人),例:
var A int = 1
func main() {
foo := func () {
A := 2
fmt.Println(A)
}
foo()
fmt.Println(A)
}
输出:
2
1
如果手误将A := 2写成了A = 2,那么输出就是:
2
2
即会影响外部变量A
- 为了将某一个私有的值传递到某个函数里,就需要在函数外声明这个值,但是这样声明会导致这个值在其他函数里也可见了(别人误操作我),例:
func main() {
A := 1
foo := func () int {
return A + 1
}
B := 1
bar := func () int {
return B + 2
}
fmt.Println(foo())
fmt.Println(bar())
}
输出:
2
3
在bar里是可以对变量A做操作的,一个不小心就容易误修改变量A
**结论:函数外的变量只能通过参数传递进去,不要通过全局变量的方式的渠道传递进去,当函数内能读取到的变量越多,出错概率(误操