匿名函数的定义和使用
匿名函数:没有指定函数名的函数声明方式
func (a, b int) int {
return a + b
}
匿名函数也可以赋值给一个变量或者直接执行:
// 1、将匿名函数赋值给变量
add := func(a, b int) int {
return a + b
}
// 调用匿名函数 add
fmt.Println(add(1, 2))
// 2、定义时直接调用匿名函数
func(a, b int) {
fmt.Println(a + b)
} (1, 2)
匿名函数与闭包
闭包:引用了自由变量(未绑定到特定对象的变量,通常在函数外定义)的函数,被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的上下文环境也不会被释放(比如传递到其他函数或对象中)。简单来说,「闭」的意思是「封闭外部状态」,即使外部状态已经失效,闭包内部依然保留了一份从外部引用的变量。
显然,闭包只能通过匿名函数实现,我们可以把闭包看作是有状态的匿名函数,反过来,如果匿名函数引用了外部变量,就形成了一个闭包(Closure)。
闭包的价值在于可以作为持有外部变量的函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示数据还要表示代码。支持闭包的语言都将函数作为第一类对象(firt-class object,有的地方也译作第一级对象、一等公民等,都是一个意思),Go 语言也不例外,这意味 Go 函数和普通 Go 数据类型(整型、字符串、数组、切片、字典、结构体等)具有同等的地位,可以赋值给变量,也可以作为参数传递给其他函数,还能够被函数动态创建和返回。
第一类对象指的是运行期可以被创建并作为参数传递给其他函数或赋值给变量的实体,在绝大多数语言中,数值和基本类型都是第一类对象,在支持闭包的编程语言中(比如 Go、PHP、JavaScript、Python 等),函数也是第一类对象,而像 C、C++ 等不支持匿名函数的语言中,函数不能在运行期创建,所以在这些语言中,函数不是第一类对象。
匿名函数的常见使用场景***
1.保证局部变量的安全性
var j int = 1
f := func() {
var i int = 1
fmt.Printf("i, j: %d, %d\n", i, j)
}
f()
j += 2
f()
2.将匿名函数作为函数参数
add := func(a, b int) int {
return a + b
}
// 将函数类型作为参数
func(call func(int, int) int) {
fmt.Println(call(1, 2))
}(add)
func main() {
...
// 普通的加法操作
add1 := func(a, b int) int {
return a + b
}
// 定义多种加法算法
base := 10
add2 := func(a, b int) int {
return a * base + b
}
handleAdd(1, 2, add1)
handleAdd(1, 2, add2)
}
// 将匿名函数作为参数
func handleAdd(a, b int, call func(int, int) int) {
fmt.Println(call(a, b))
}
这样一来,就可以通过一个函数执行多种不同加法实现算法,提升了代码的复用性,我们可以基于这个功能特性实现一些更复杂的业务逻辑,比如 Go 官方 net/http
包底层的路由处理器也是这么实现的
第二个匿名函数 add2
引用了外部变量 base
,形成了一个闭包,在调用 handleAdd
外部函数时传入了闭包 add2
作为参数,add2
闭包在外部函数中执行时,虽然作用域离开了 main
函数,但是还是可以访问到变量 base
。
3.将匿名函数作为函数返回值
// 将函数作为返回值类型
func deferAdd(a, b int) func() int {
return func() int {
return a + b
}
}
func main() {
...
// 此时返回的是匿名函数
addFunc := deferAdd(1, 2)
// 这里才会真正执行加法操作
fmt.Println(addFunc())
}
调用 deferAdd
函数返回的是一个匿名函数,但是这个匿名函数引用了外部函数传入的参数,因此形成闭包,只要这个闭包存在,这些持有的参数变量就一直存在,即使脱离了 deferAdd
函数的作用域,依然可以访问它们。
另外调用 deferAdd
方法时并没有执行闭包,只有运行 addFunc()
时才会真正执行闭包中的业务逻辑(这里是加法运算),因此,我们可以通过将函数返回值声明为函数类型来实现业务逻辑的延迟执行,让执行时机完全掌握在开发者手中。