匿名函数同样被称之为闭包(函数式语言的术语):它们被允许调用定义在其它环境下的变量。闭包可使得某个函数捕捉到一些外部状态,例如:函数被创建时的状态。
另一种表示方式为:一个闭包继承了 函数所声明时的作用域。这种状态(作用域内的变量)都被共享到闭包的环境中,因此这些变量可以在 闭包中被操作,直到被销毁。闭包经常被用作包装函数:它们会预先定义好 1 个或多个参数以用于包 装。另一个不错的应用就是使用闭包来完成更加简洁的错误检查。
仅仅从形式上将闭包简单理解为匿名函数是不够的,还需要理解闭包实质上的含义。
实质上看,闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。闭包在运行时 可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。由闭包的实质含义,我们可 以推论:闭包获取捕获变量相当于引用传递,而非值传递;对于闭包函数捕获的常量和变量,无论闭包 何时何处被调用,闭包都可以使用这些常量和变量,而不用关心它们表面上的作用域。
引用环境验证
package main
import "fmt"
func addNumber(x int) func(int) {
fmt.Printf("x :%d addr of x:%p\n", x, &x)
return func(y int) {
k := x + y
x = k
y = k
fmt.Printf("x :%d addr of x:%p\n", x, &x)
fmt.Printf("y :%d addr of y:%p\n", y, &y)
}
}
func main() {
addNum := addNumber(5)
addNum(1)
addNum(1)
addNum(1)
fmt.Println("-------------------------------")
addNum2 := addNumber(5)
addNum2(1)
addNum2(1)
addNum2(2)
}
output:
x :5 addr of x:0xc00000a0c8
x :6 addr of x:0xc00000a0c8
y :6 addr of y:0xc00000a110
x :7 addr of x:0xc00000a0c8
y :7 addr of y:0xc00000a128
x :8 addr of x:0xc00000a0c8
y :8 addr of y:0xc00000a140
-------------------------------
x :5 addr of x:0xc00000a158
x :6 addr of x:0xc00000a158
y :6 addr of y:0xc00000a168
x :7 addr of x:0xc00000a158
y :7 addr of y:0xc00000a180
x :9 addr of x:0xc00000a158
y :9 addr of y:0xc00000a198
根据上面代码结果,发现X在匿名函数声明时传入,是引用该参数。后续调用同一个匿名函数x地址不变,但值会改变。而y值作为匿名函数参数每次调用都传值,地址在改变。
斐波那契数
import "fmt"
func test(a, b int) func() int {
return func() int {
a, b = b, a+b
return a
}
}
func main() {
var a, b int = 1, 1
c := test(a, b)
for i := 0; i < 10; i++ {
fmt.Println(c())
}
}
output:
1
2
3
5
8
13
21
34
55
89
使用闭包调试:
方法1:
当您在分析和调试复杂的程序时,无数个函数在不同的代码文件中相互调用,如果这时候能够准确地知 道哪个文件中的具体哪个函数正在执行,对于调试是十分有帮助的。您可以使用 runtime 或 log 包 中的特殊函数来实现这样的功能。包 runtime 中的函数 Caller() 提供了相应的信息,因此可以在 需要的时候实现一个 where() 闭包函数来打印函数执行的位置:
package main
import (
"fmt"
"runtime"
)
func main() {
where := func() {
_, file, line, _ := runtime.Caller(1)
fmt.Printf("%s:%d\n", file, line)
}
where()
fmt.Println("---next---")
where()
}
output:
H:/awesomeProject/foo/go42.go:13
---next---
H:/awesomeProject/foo/go42.go:15
方法2:
使用log包的打印函数
package main
import (
"fmt"
"log"
)
func main() {
log.SetFlags(log.Llongfile | log.LstdFlags)
where := log.Print
where()
fmt.Println("---next---")
where()
}
output:
2020/10/12 21:46:44 H:/awesomeProject/foo/go42.go:11:
---next---
2020/10/12 21:46:44 H:/awesomeProject/foo/go42.go:13: