当前位置:网站首页>golang学习之四:闭包、defer
golang学习之四:闭包、defer
2022-06-10 04:11:00 【java_xxxx】
闭包
闭包以引用的方式捕捉外部变量
package main
import "fmt"
func main() {
a := 10
str := "狂歌痛饮空度日"
func() {
a = 14
str = "飞扬跋扈为谁雄"
fmt.Printf("闭包:a= %d, str = %s\n", a, str)
}()// 函数调用
fmt.Printf("外部:a= %d, str = %s\n", a, str)
}
控制台打印
闭包:a= 14, str = 飞扬跋扈为谁雄
外部:a= 14, str = 飞扬跋扈为谁雄
PS D:\vscode\code\demo1>
以上例子说明,闭包以引用的方式捕捉外部变量,闭包里更改了变量,实则是引用的方式,所以外面的变量也跟着改变了。
闭包变量与作用域
所谓闭包就是一个函数“捕获”了和它在同一作用域的其它常量和变量。这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包能够使用这些常量或者变量。它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用它,这些变量就还会存在。
下面我们来看两个例子去理解一下上面这句话
传统函数局部变量
package main
import "fmt"
func test1() int {
// 函数被调用时,x才分配空间,才初始化为0
var x int // int 类型没有被初始化,值为0
x++
return x * x // 函数调用完毕,x 自动释放
}
func main() {
fmt.Println(test1())
fmt.Println(test1())
fmt.Println(test1())
fmt.Println(test1())
fmt.Println(test1())
}
控制台
PS D:\vscode\code\demo1> go run bibao2.go
1
1
1
1
1
以上代码没有什么好说的,就是我们常见的函数,其中要注意一点是,在golang里,函数的局部变量是在函数被调用时才,变量才初始化,比如int类型的变量,然后初始化为0,当函数调用完毕之后,这些局部变量就会被释放。如上面例子,调用多少次,那么x就初始化,然后释放,下次调用的时候仍然是初始化,然后再释放。
下面我们来看看闭包的变量
闭包的变量
package main
import "fmt"
// 函数的返回值是一个匿名函数,返回一个函数类型
func test2() func() int {
var x int
// 对于下面的代码,匿名函数就形成了一个闭包了
return func() int {
x++
return x * x
}
}
func main() {
// 返回值为一个匿名函数,返回一个函数类型,通过f来调用返回的匿名函数,f来调用闭包函数
f := test2()
// 它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包(这里指的是f)还在使用它,这些变量就还会存在
fmt.Println(f()) // 1
fmt.Println(f()) // 4
fmt.Println(f()) // 9
fmt.Println(f()) // 16
fmt.Println(f()) // 25
}
上面代码,在闭包第一次被调用完之后,一下代码就形成了一个独立的空间
return func() int {
x++
return x * x
}
x 的生命周期还在,也没有被释放。那x还是1,当第一次调用之后,x=1,第二次调用的时候x++ 就是2,然后第三次x++ = 3…所以返回结果就是他们的平方,1,4,9,16,25.
函数test2返回另一个类型为func() int 的函数。对test2的一次调用会生成一个局部变量x并返回一个匿名函数。每次调用时匿名函数时,该函数都会先使x的值加1,再返回x的平方。第二次调用test2 时,会生成第二个x变量,并返回一个新的匿名函数。新匿名函数操作的是第二个x变量。
通过这个例子,我们看到变量的生命周期不由它的作用域决定:test2返回后,变量x仍然隐式的存在于f中。
所以对于开始的那句话:它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只要闭包函数(这里指main方法的f)还在使用它,这些变量就还会存在。
defer
官方定义:关键字 defer用于延迟一个函数或者方法(或者当前所创建的匿名函数)的执行。注意,defer语句只能出现在函数或方法的内部。
说人话:defer主要用于函数在调用结束前做一些清理工作,比如我们读取文件,文件打开了,然后我要关闭文件,什么时候关闭呢,那就是函数结束前去关闭。
延迟:在函数执行完毕之前调用
defer是一个延迟的作用,在函数执行完毕之前调用,所以defer修饰的代码会被在最后执行,如下
package main
import "fmt"
func main() {
// defer是一个延迟的作用,在函数执行完毕之前调用,所以defer修饰的代码会被在最后执行,如下
defer fmt.Println("我自横刀向天笑")
fmt.Println("去留肝胆两昆仑")
}
控制台:
PS D:\vscode\code\demo1> go run defer1.go
去留肝胆两昆仑
我自横刀向天笑
多个defer执行顺序
如果一个函数中有多个defer语句,它们会以LFO(后进先出)的顺序执行。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。
其实可以理解为和栈差不多,先进后出,就是按照代码顺序,先defer的后被执行。
下面我们来看一段代码
package main
import "fmt"
func test(x int) {
result := 10 / x
fmt.Println("result = %d", result)
}
func main() {
// defer是一个延迟的作用,在函数执行完毕之前调用,所以defer修饰的代码会被在最后执行,如下
fmt.Println("不尽长江滚滚来")
fmt.Println("无边落木萧萧下")
test(0)
fmt.Println("驻青沙白鸟飞回")
fmt.Println("风急天高猿啸哀")
}
下面我们通过代码来解释一下这句话:
如果一个函数中有多个defer语句,它们会以LFO(后进先出)的顺序执行。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。
demo1:普通代码,没有defer
PS D:\vscode\code\demo1> go run defer1.go
不尽长江滚滚来
无边落木萧萧下
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.test(0xf366c0)
D:/vscode/code/demo1/defer1.go:6 +0xad
main.main()
D:/vscode/code/demo1/defer1.go:15 +0x9b
exit status 2
demo分析:以上代码没有defer我们可以看到,以上代码遇到panic的时候就停止运行了。
demo2:其他代码加defer,但是panic的那行代码不加defer
package main
import "fmt"
func test(x int) {
result := 10 / x
fmt.Println("result = %d", result)
}
func main() {
// defer是一个延迟的作用,在函数执行完毕之前调用,所以defer修饰的代码会被在最后执行,如下
defer fmt.Println("不尽长江滚滚来")
defer fmt.Println("无边落木萧萧下")
test(0)
defer fmt.Println("驻青沙白鸟飞回")
defer fmt.Println("风急天高猿啸哀")
}
控制台
PS D:\vscode\code\demo1> go run defer1.go
无边落木萧萧下
不尽长江滚滚来
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.test(0x101018c6cbd0108)
D:/vscode/code/demo1/defer1.go:6 +0xad
main.main()
D:/vscode/code/demo1/defer1.go:15 +0xc5
exit status 2
PS D:\vscode\code\demo1>
demo2分析:代码里除了panic的那行代码没有defer,其他的都加了defer。我们可以看到两个现象。现象1:前两句诗确实是按照 先进后出,即多个defer修饰的代码先定义后打印。
现象2,panic之后的代码并没有被打印,也就是在panic之后的代码就没运行。
注意:以上代码其实最先调用的是test(0)这行代码,因为被defer修饰代码在函数运行结束之前才会被调用。其次运行到代码test(0)的时候代码崩了,然后函数就要结束了,所以此时要运行被defer修饰的代码,因为test(0)没有被defer修饰,所以test(0)之后的代码就不会被运行了。于是打印结果是只打印了,前两个derfer。所以我们在写代码的时候,尽量将defer写在函数最前面。
demo3:所有代码都加defer
package main
import "fmt"
func test(x int) {
result := 10 / x
fmt.Println("result = %d", result)
}
func main() {
// defer是一个延迟的作用,在函数执行完毕之前调用,所以defer修饰的代码会被在最后执行,如下
defer fmt.Println("不尽长江滚滚来")
defer fmt.Println("无边落木萧萧下")
defer test(0)
defer fmt.Println("驻青沙白鸟飞回")
defer fmt.Println("风急天高猿啸哀")
}
控制台
PS D:\vscode\code\demo1> go run defer1.go
风急天高猿啸哀
驻青沙白鸟飞回
无边落木萧萧下
不尽长江滚滚来
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.test(0xab6220)
D:/vscode/code/demo1/defer1.go:6 +0xad
main.main()
D:/vscode/code/demo1/defer1.go:18 +0x19a
exit status 2
PS D:\vscode\code\demo1>``` demo3分析: 现象1:多个defer修饰的代码全部是,【先进后出】即:先定义后运行的顺序。 现象2:哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。 所以以上3个demo就解释了开头的那句话: 如果一个函数中有多个defer语句,它们会以LFO(后进先出)的顺序执行。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。 # defer和匿名函数联合使用 无奖精彩,爱猜不猜 dem1:你知道运行结果吗? ```go
package main
import "fmt"
func test(x int) {
result := 10 / x
fmt.Println("result = %d", result)
}
func main() {
a := 10
b := 20
defer func() {
fmt.Printf("匿名函数 a=%d, b=%d\n", a, b)
}() // 调用函数
a = 111
b = 222
fmt.Printf("外部 a = %d, b =%d\n", a, b)
}
控制台:
PS D:\vscode\code\demo1> go run defer2.go
外部 a = 111, b =222
匿名函数 a=111, b=222
PS D:\vscode\code\demo1>
demo1分析,首先defer是最后执行的,所以先打印外部函数,再打印匿名函数。又因为两个变量已经被赋予了新的值,于是defer打印的就是新的值了。
有人说demo1很简单,那我们来看看demo2
demo2:
package main
import "fmt"
func test(x int) {
result := 10 / x
fmt.Println("result = %d", result)
}
func main() {
a := 10
b := 20
defer func(a, b int) {
fmt.Printf("匿名函数 a=%d, b=%d\n", a, b)
}(a, b) // 调用函数
a = 111
b = 222
fmt.Printf("外部 a = %d, b =%d\n", a, b)
}
控制台
PS D:\vscode\code\demo1> go run defer2.go
外部 a = 111, b =222
匿名函数 a=10, b=20
PS D:\vscode\code\demo1>
WTF?
demo2分析:首先先打印外部的,并且外部打印的为111,222这个没啥争议。
但是匿名函数内呢?其实代码相当于是先把参数10,20传进去了,但是并没有马上运行,因为它被defer修饰了,所以它最后运行,但是当运行的时候,它拿到的还是传递过去的10,20,所以打印结果是10,20。
以上代码就等价于
defer func(a, b int) {
fmt.Printf("匿名函数 a=%d, b=%d\n", a, b)
}(10, 20) // 调用函数
defer func(a, b int) {
fmt.Printf("匿名函数 a=%d, b=%d\n", a, b)
}(a, b) // 调用函数
demo3
package main
import "fmt"
func f1() (result int) {
defer func() {
result++
}()
return 1
}
func f2() (r int) {
t := 1
defer func() {
t++
}()
return t
}
func f3() (r int) {
defer func(t int) {
t++
}(r)
return 1
}
func main() {
fmt.Println(f1())
fmt.Println(f2())
fmt.Println(f3())
}
demo3
控制台
PS D:\vscode\code\demo1> go run defer3.go
2
1
1
demo3解析:
package main
import "fmt"
// 首先函数的返回值为result,在return 1 的时候,其实是将1 赋值给了result这个变量,然后defer是最后运行的,
// result++ ,所以返回值为2
func f1() (result int) {
defer func() {
result++
}()
return 1
}
// 首先函数的返回值为r,在return的时候,其实是将t的值赋值给了r,于是r = 1,然后defer是最后运行的
// 但是返回值为r,defer里面对t进行++,t = 2,但是返回值是r,所以函数返回值为1
func f2() (r int) {
t := 1
defer func() {
t++
}()
return t
}
// 首先函数返回值为r, return 1的时候其实是将 1赋值给了r,然后函数传参,将r 传递了进去,此时t = 1,然后t++ = 2
// 但是函数的返回值为r,与t无关,所以返回1,可以把defer函数里的r参数看成一个固定值1,就行了,不要被干扰
// 还可以这样理解,go语言中所有的函数传递都是传值的,那么所有函数都是有一个参数副本的,在函数里面计算是计算的副本,
// 不会修改原先的值,也就是这里虽然传递的是r,但是它算的是r的副本,并不是r原先的值,如果这里传递的是r的引用,那结果就是2了
func f3() (r int) {
defer func(t int) {
t++
}(r)
return 1
}
// 如果传递的是引用,则defer里计算的就是r的值了,那么函数返回的就是r了。
// 注意:*类型,如*int代表该变量为指针类型,用来存储变量的地址。*指针类型的*代表对指针取值,取出指针指向的地址的内存。&代表取一个变量的指针地址
func f3() (r int) {
defer func(t *int) {
*t++
}(&r)
return 1
}
func main() {
fmt.Println(f1())
fmt.Println(f2())
fmt.Println(f3())
}
边栏推荐
- 阿里云首次年度盈利,国内云厂商何时迎来集体回报期?
- [MySQL] several methods of multi table Association and foreign key problems, multi table query and sub query
- 【分库分表三部曲精华】
- Fleeing Beijing, Shanghai and Guangzhou at the age of 35, and giving away food when unemployed at the age of 40, the "dignity" of middle-aged people lies in investing in themselves
- Encrypting CEPH RBD using luks2
- As a software testing engineer, give advice to young people (Part 1)
- How to write Scala code in idea
- [从零开始学习FPGA编程-14]:快速入门篇 - 操作步骤3(功能仿真)-3-modelsim快速使用入门(8bits循环计数器)
- MySQL - Installation
- Mengxiaofeng, a Chinese encryption artist, participated in the "liquid alloy" meta universe painting exhibition held in Italy
猜你喜欢
![[mysql] database - View](/img/5c/fc38c4a5e541810b1606e3d2a762b7.png)
[mysql] database - View

外观设计产品用途
![[understanding of opportunity -16]: Pyramid hierarchy conforms to nature](/img/d3/799e67b6dc825758927191d2ef8c4f.jpg)
[understanding of opportunity -16]: Pyramid hierarchy conforms to nature

document editor

【OV7670】基于FPGA的OV7670摄像头介绍和使用

汇编:汇编指令分类

为什么使用三层交换机

Encrypting CEPH RBD using luks2

Qpprogressbar+qpushbutton+qmainwindow+qtmer+ layout manager +qtextcodec+qtexttospeech in QT

【组队学习】【38期】组队学习内容详情!
随机推荐
Is the online account opening channel reliable? Is it safe?
Code error writing method of wechat applet rotation chart
Online JSON to CSV tool
How to open an account on your mobile phone? Is it safe to open an account online?
Qpprogressbar+qpushbutton+qmainwindow+qtmer+ layout manager +qtextcodec+qtexttospeech in QT
redisson yml配置出错
[learn FPGA programming from scratch -13]: quick start chapter - operation step 3 (functional simulation) -2-mentor HDL simulation tool introduction to Modelsim tool and basic principle of functional
[understanding of opportunity -16]: Pyramid hierarchy conforms to nature
[adaptive motion compensation] FPGA based adaptive motion compensation video image enhancement system
5- common tool management
[Error] anonymous type with no linkage used to declare function ‘bool InitSLinkList
Google Earth engine (GEE) - gpwv411: data set of average administrative unit area
35岁逃离北上广,40岁失业送外卖,中年人的“体面”在于投资自己
[深入研究4G/5G/6G专题-29]: 5G NR开机流程5.1 - NR网络架构、基站的核心网相关配置
为什么使用三层交换机
[mysql] database - View
v-lazy
[in depth study of 4g/5g/6g topic -26]: 5g NR startup process 4.4 - scheduling of RRC connection response message msg4 (rrcsetup authorization) and detailed explanation of message content
[从零开始学习FPGA编程-14]:快速入门篇 - 操作步骤3(功能仿真)-3-modelsim快速使用入门(8bits循环计数器)
4-mirror address