Go 学习笔记(18)— 函数(04)[闭包定义、闭包修改变量、闭包记忆效应、闭包实现生成器、闭包复制原对象指针]

1. 闭包定义

Go 语言中闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量,因此,简单的说:

函数 + 引用环境 = 闭包

一个函数类型就像结构体一样,可以被实例化,函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有“记忆性”,函数是编译期静态的概念,而闭包是运行期动态的概念

Go 语言闭包函数的定义:当匿名函数引用了外部作用域中的变量时就成了闭包函数,闭包函数是函数式编程语言的核心。

也就是匿名函数可以会访问其所在的外层函数内的局部变量。当外层函数运行结束后,匿名函数会与其使用的外部函数的局部变量形成闭包。

2. 闭包示例

2.1 闭包修改变量

闭包对它作用域上部的变量可以进行修改,修改引用的变量会对变量进行实际修改,通过下面的例子来理解:

package main

import "fmt"

func main() {
	// 准备一个字符串
	str := "hello world"
	fmt.Println("str is ", str)

	// 创建一个匿名函数
	foo := func() {

		// 匿名函数中访问str
		// 在匿名函数中并没有定义 str,str 的定义在匿名函数之前,此时,str 就被引用到了匿名函数中形成了闭包。
		str = "hello dude"
	}

	// 调用匿名函数, 执行闭包,此时 str 发生修改,变为 hello dude。
	foo()
	fmt.Println("str is ", str)
}

代码输出:

str is  hello world
str is  hello dude

2.2 闭包记忆效应

被捕获到闭包中的变量让闭包本身拥有了记忆效应,闭包中的逻辑可以修改闭包捕获的变量,变量会跟随闭包生命期一直存在,闭包本身就如同变量一样拥有了记忆效应。

package main

import (
	"fmt"
)

/*
累加器生成函数,这个函数输出一个初始值,
调用时返回一个为初始值创建的闭包函数
*/
func Accumulate(value int) func() int {
	// 返回一个闭包,每次返回会创建一个新的函数实例
	return func() int {
		// 累加,对引用的 Accumulate 参数变量进行累加,注意 value 不是上一行匿名函数定义的,但是被这个匿名函数引用,所以形成闭包。
		value++
		// 返回一个累加值
		return value
	}
}

func main() {
	// 创建一个累加器,初始值为 1,返回的 accumulator 是类型为 func()int 的函数变量。
	accumulator := Accumulate(1)
	// 累加1并打印
	fmt.Println(accumulator())
	fmt.Println(accumulator())
	// 打印累加器的函数地址
	fmt.Printf("%p\n", &accumulator)

	// 创建一个累加器, 初始值为10
	accumulator2 := Accumulate(10)
	// 累加1并打印
	fmt.Println(accumulator2())
	// 打印累加器的函数地址
	fmt.Printf("%p\n", &accumulator2)
}

输出结果:

2
3
0xc00000e030
11
0xc00000e040

可以看出 accumulator 与 accumulator2 输出的函数地址不同,因此它们是两个不同的闭包实例。

package main

import "fmt"

func add() func(int) int {
	var x int
	return func(y int) int {
		x += y
		return x
	}
}
func main() {
	var f = add()
	fmt.Println(f(1)) //1
	fmt.Println(f(2)) //3
	fmt.Println(f(3)) //6

	f1 := add()
	fmt.Println(f1(4)) //4
	fmt.Println(f1(5)) //9
}

add() 函数赋值给变量 f,那么 f(1) 就相当于执行其内部的匿名函数 func(y int)。所以 f(1) 其实执行的是 x = x+y,此时 x 没有给值默认为 0,y 为 1,最终返回 x 的值是 1。

f(2) 同样执行其内部的匿名函数 func(y int),所以 f(2) 其实执行的是 x = x+y。此时 x 已经是 1,y 为 2,最终返回 x 的值是 3,依次类推执行。

2.3 闭包实现生成器

玩家生成器的实现:

package main

import (
	"fmt"
)

// 创建一个玩家生成器, 输入名称, 输出生成器
func playerGen(name string) func() (string, int) {
	// 血量一直为150
	hp := 150
	// 返回创建的闭包
	return func() (string, int) {
		// 将 hp 和 name 变量引用到匿名函数中形成闭包。
		return name, hp
	}
}

func main() {
	// 创建一个玩家生成器
	generator := playerGen("wohu")
	// 返回玩家的名字和血量
	name, hp := generator()
	// 打印值
	fmt.Println(name, hp)   // wohu 150
}

2.4 闭包复制原对象指针

闭包复制的是原对象指针,这就很容易解释延迟引用现象。

package main

import "fmt"

func test() func() {
	x := 100
	fmt.Printf("x (%p) = %d\n", &x, x)
	return func() {
		fmt.Printf("x (%p) = %d\n", &x, x)
	}
}

func main() {
	f := test()
	f()
}

输出:

x (0xc000018068) = 100
x (0xc000018068) = 100

3. 对比 Python 闭包

def fun1():
   sum = 0
   def fun2(v):
          nonlocal sum
       sum += v
       return sum
   return fun2

a = fun1()
for i in range(10):
   print(a(i))

0
1
3
6
10
15
21
28
36
45

代码解析:
fun1 返回的不是一个值,而是一个函数 fun2,a = fun2,所以 a(i) 会打印 sum 的值。

为什么 sum 一直在加呢,函数里的值为什么可以带到函数体外呢?
其实可以把闭包看做一个类, sum 就是类里的属性,fun2 就是类的方法,所以 fun2 可以使用 sum(自由变量)。

nonlocal 声明的变量不是局部变量,也不是全局变量,而是外部嵌套函数内的变量。嵌套函数 fun2 中的 sum 受到了影响,显示 fun2 中的 sum 是 fun1 函数中的局部变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值