回调函数和闭包
高阶函数
golang中的高阶函数有以下特性:
- 函数可以作为另一个函数的参数(常用于回调函数)
- 函数可以作为另一个函数的返回值(常用于闭包)
- 函数可以被赋值给一个变量
1.函数作为参数
1 | package main |
此例中将在main函数中定一个匿名函数,将该函数赋值给变量f,作为参数传入added函数中,added函数会接收一个func(a, b int) int类型的函数作为参数。
这种将函数b作为参数传入函数a的使用方式可以提高函数a的灵活性,只要函数b符合函数a定义的参数类型就可以通过b完成很多不同的功能,因此常用于回调
2.函数作为返回值
1 | func squares() func() int { |
这里在函数体内部定义一个匿名函数,并且将该匿名函数作为另一个函数的返回值。main函数中将squares函数赋值给变量f2。
每一次打印都会调用一次squares函数,生成一个新的局部变量x,但是匿名函数会记录x的状态,每一次x都是在之前的基础上加1。
函数值就被称为闭包
回调函数
go语言中sort包内的SliceStable()就是一个回调函数,它用于给slice中的元素进行排序。该函数定义如下:
1 | func SliceStable(slice interface{}, less func(i, j int) bool) |
该函数的第二个参数就是一个func(i, j int) bool类型的名为less的函数。
less方法对已知的slice中的i,j索引对应的元素进行排序。
我们可以通过不同的less函数的实现对slice进行不同的排序方式。
闭包
函数a返回函数b的经典场景就是闭包。
闭包 -> 函数+作用域环境
闭包就是包含以上两个要素的特殊函数,它可以访问不是在它内部声明的变量,这个变量位于其他的作用域并且是未赋值的。
1 | package main |
- x不在g的作用域内,并且g引用了x,所以g是一个闭包。
- 如果x在传给g之前就被赋值了,g就不能称为闭包了
闭包特性:
对于每个闭包g来说,不同的g引用不同的x对应的数据对象。或者说f函数的x对应的数据对象对于每个不同的闭包是不一样的。
1
2a := f(1)
b := f(1)以上a和b是两个闭包,两个闭包中的自由变量的数值都是1,但它们不是同一个数值对象
只要不是匿名闭包,就可以一直访问x对应的同一个数据对象
1
2
3
4
5
6
7a := f(1)//a是一个有名称的闭包,会一直引用同一个数值对象1
a(3) //调用闭包a,返回1+3=4,1是一直被闭包a引用的数值对象,执行完以后也不会放开
a(3) //再次调用闭包a,依旧返回4,调用还是跟上一次调用a时相同的数值对象1
f(1)(3) //匿名的闭包,调用完后就会把数值对象1放开
f(1)(3) //引用的数值对象1与上一次匿名闭包引用的是不同的数值对象,与上一个匿名闭包相互独立虽然g()中持有了自由变量,但是g()函数自身不是闭包函数,只有在g持有的自由变量x和传递给f()函数的x的值(即f(1)中的1)进行绑定的时候,才会从g()创建一个闭包函数,这表示闭包函数开始引用这个自由变量,并且这个闭包一直持有这个变量的引用,即使f()已经执行完毕了。
闭包不一定要通过另一个函数返回,可以直接自定义一个闭包,只要它访问了一个不在自己作用域并且尚未被赋值的变量即可。
1 | func main() { |
闭包的作用:让多个闭包函数共享一个自由变量。
例如在第一个例子中,a := f(3),给外层的变量x赋值为3,那么a(3),a(5),a(8)….所有闭包都共享这个值
什么时候使用闭包?
一般来说,可以将过程分成两部分或更多部分都进行工厂化的时候,就适合闭包(实际上,有些地方直接将闭包称呼为工厂函数)。第一个部分是可以给自由变量批量绑定不同的值,第二部分是多个闭包函数可以共享第一步绑定后的自由变量。