go语言学习笔记 — 函数(7):闭包(closure)—— 引用了外部变量的匿名函数

闭包是引用了(函数外部)自由变量的函数。被引用的自由变量和函数一同存在,即使离开了自由变量所在的引用环境(例如在某个go源文件中导入某个package的闭包),我们仍可在闭包中继续使用这个自由变量。自由变量是闭包函数外部作用域中的变量,可能是全部变量、函数入参、局部变量。

简而言之,闭包 = 函数 + 引用环境。同一个函数可以与不同引用环境结合,形成不同的闭包实例。

go语言学习笔记 — 函数(7):闭包(closure)—— 引用了外部变量的匿名函数_第1张图片

闭包具有以下特点:

  • 函数像结构体一样,可以被实例化成一个闭包

  • 函数本身并不存储任何数据,只有与引用环境相结合形成闭包后,才具有“记忆性”

  • 函数是编译时的静态概念,而闭包是运行时的动态概念


1. 在闭包内部修改引用的变量

函数引用其变量作用域上的变量,形成闭包。修改闭包内的引用变量会对自由变量进行实际修改。

package func_test

import (
	"fmt"
	"testing"
)

// 准备一个字符串
var str = "hello world"

// 创建一个匿名函数
var foo = func() {
	str = "hello china"
	fmt.Println(str)
}

func TestClosure(t *testing.T) {
	foo()
	fmt.Println(str)
}

自由变量str不是在匿名函数中,而是在匿名函数之前定义。自由变量str被引用到匿名函数中,形成闭包。修改闭包内的变量str,会修改自由变量。


2. 闭包的记忆效应

被引用的自由变量让闭包拥有了记忆效应。我们可以在闭包中修改所引用的自由变量,这个变量会和闭包一直存在,而闭包就像变量一样拥有记忆效应。

package func_test

import (
	"fmt"
	"testing"
)

// 定义累加器生成函数Accumulate
func Accumulate(value int) func() int {
	return func() int { // 返回一个闭包,这个闭包是一个匿名函数引用了装饰者函数的入参
		value++
		return value // 返回被修改后的自由变量
	}
}

func TestClosureMemory(t *testing.T) {
	accumulator := Accumulate(1) // 创建一个闭包实例accumulator,引用入参1
	fmt.Println(accumulator())   // 调用闭包实例,并打印闭包中被修改后的变量,证明闭包的记忆性
	fmt.Println(accumulator())
	fmt.Printf("%p\n", accumulator) // 打印闭包的内存地址

	accumulator2 := Accumulate(10)   // 创建一个闭包实例accumulator,引用入参10
	fmt.Println(accumulator2())      // 调用闭包实例,并打印闭包中被修改后的变量,证明闭包的记忆性
	fmt.Printf("%p\n", accumulator2) // 打印闭包的内存地址
}

3. 闭包实现生成器

package func_test

import (
	"fmt"
	"testing"
)

// 创建一个玩家生成器,输入名称,输出生成器
func playGen(name string) func() (string, int) {
	hp := 150 // hp是playGen函数的局部变量(不能被playGen函数的外部访问和修改),也是被匿名函数引用的自由变量。
	return func() (string, int) { // 返回匿名函数
		return name, hp // 匿名函数引用外部变量name和hp,形成闭包
	}
}

func TestGenerator(t *testing.T) {
    // 生成一个玩家生成器实例(闭包)
    generator := playGen("high noon") 
	
	name, hp := generator()    // 调用闭包,获取记忆变量
	fmt.Println(name, hp)
}

你可能感兴趣的:(Golang)