匿名函数和闭包|Go主题月

383 阅读2分钟

匿名函数

什么是匿名函数?字面意义上说明就是没有被赋予名称的函数,举个例子:

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已经逃逸到堆上了。