匿名函数
什么是匿名函数?字面意义上说明就是没有被赋予名称的函数,举个例子:
package main
import (
"fmt"
)
func main() {
// 匿名函数定义方式1
func (s string){
fmt.Printf("匿名函数:%s\n",s)
}("定义方式1")
// 匿名函数定义方式2
fn:=func (s string){
fmt.Printf("匿名函数:%s\n",s)
}
fn("定义方式2")
}
闭包
闭包则实际上是一个函数的实例,也就是说它是存在于内存里的某个结构体。如果从实现上来看的话,匿名函数如果没有捕捉自由变量,那么它其实可以被实现为一个函数指针,或者直接内联到调用点,如果它捕捉了自由变量那么它将是一个闭包;
而闭包则意味着同时包括函数指针和环境两个关键元素。在编译优化当中,没有捕捉自由变量的闭包可以被优化成普通函数,这样就无需分配闭包结构体,这种编译技巧被称为函数跃升。
闭包=函数+引用环境
package main
import (
"fmt"
)
func main() {
// c拷贝闭包副本
c:=Closure()
c() // 调用闭包
c() // 调用闭包
// c2会拷贝新的闭包副本
c2:=Closure()
c2() // 调用闭包
}
/*
闭包
*/
func Closure() func(){
i:=1
return func() {
i++
fmt.Printf("闭包,i=%d\n",i)
}
}
输出:
闭包,i=2
闭包,i=3
闭包,i=2
逃逸分析
go语言能通过escape analyze识别出变量的作用域,自动将变量在堆上分配。将闭包环境变量在堆上分配是Go实现闭包的基础。
可以通过如下指令来分析上面的代码:
go build --gcflags=-m main.go
输出:
# command-line-arguments
./main.go:23:13: inlining call to fmt.Printf
./main.go:20:2: moved to heap: i
./main.go:21:9: func literal escapes to heap
./main.go:23:14: i escapes to heap
./main.go:23:13: []interface {}{...} does not escape
<autogenerated>:1: .this does not escape
可以看到**./main.go:23:14: i escapes to heap**,表明变量i已经逃逸到堆上了。