golang中的defer函数理解
怎么入门Golang编程?需要学习哪些知识点?这是新手们刚接触编程时常见的问题;下面golang学习网就来给大家整理分享一些知识点,希望能够给初学者一些帮助。本篇文章就来介绍《golang中的defer函数理解》,涉及到函数、defer,有需要的可以收藏一下
golang的defer
什么是defer
defer的的官方文档:https://golang.org/ref/spec#Defer_statements
go语言中defer可以完成延迟功能,当前函数执行完成后再执行defer的代码块。通过defer,我们可以在代码中优雅的关闭/清理代码中所使用的变量。
defer是Go语言中的延迟执行语句,用来添加函数结束时执行的代码,常用于释放某些已分配的资源、关闭数据库连接、断开socket连接、解锁一个加锁的资源。
Go语言机制担保一定会执行defer语句中的代码。其它语言中也有类似的机制,比如Java、C#语言里的finally语句,C++语言里的析构函数(Destructor)可以起类似的作用,C++语言机制担保在对象被销毁前一定会执行析构函数中的代码。C++中的析构函数析构的是对象,Go中的defer析构的是函数。
理解defer
defer什么时间执行(defer、 return、返回值 三者的执行顺序)
defer只有在当前函数执行完毕后,才会执行。描述其实不太精确
go中的return语句并不是原子性操作,一般是分为两步:
- 将返回值赋值给一个变量
- 执行RET指令
return并不是原子性操作,是通过一个变量赋值和ret指令来完成的。defer就执行在1之后,2之前。defer的执行顺序在return之后,但是在返回值返回给调用方之前,所以使用defer可以达到修改返回值的目的。
defer、 return、返回值 三者的执行顺序是 : return 最先给返回值赋值;接着 defer 开始执行一些收尾工作;最后 RET 指令携带返回值退出函数。
package main
import (
"fmt"
)
func main() {
ret := test()
fmt.Println("test return:", ret)
}
// func test() ( int) { 这种就是匿名返回值
//返回值改为命名返回值, 具名返回值。即返回值带有名字, 这样我们在执行defer的时候相当于修改了返回值的值
func test() (i int) {
//var i int
defer func() {
i++
fmt.Println("test defer, i = ", i)
}()
return i
}
注意: 这块验证使用了具名返回值
func test() (i int) {中的(i int)。 测试结果满足我们预期。
编码中,我们要特别注意, go语言中匿名返回值和命名返回值对defer的影响。不过一般我们都是使用命名返回值。
一个主函数拥有一个匿名的返回值,返回时使用字面值,比如返回”1”、”2”、”Hello”这样的值,这种情况下defer语句是无法操作返回值的。
defer输出的值,就是定义时的值。而不是defer真正执行时的变量值(注意引用情况)
defer函数会在return之后被调用。那么这段函数执行完之后,是不用应该输出1呢?
package main
import "fmt"
func test1() {
i := 0
defer fmt.Println(i)
i++
return
}
func main() {
test1()
}
输出结果:0
虽然我们在defer后面定义的是一个带变量的函数: fmt.Println(i). 但这个变量(i)在defer被声明的时候,就已经确定其确定的值了。
总结: 因为defer后面的函数在入栈的时候保存的是入栈那一刻的值,而当时i的值是0,所以后期对i修改,并不会影响栈内函数的值。
我们再看下一个例子:
package main
import "fmt"
func test2() {
x := 10
defer func(a *int) {
fmt.Println(*a)
}(&x)
x++
}
func main() {
test2()
}
输出结果: 11
这里为什么和前面结论不一样呢?
这里defer后面函数入栈的时候存入的执行变量x的指针。所以,后期x值改变的时候,输出结果也会改变。
总结: 需要注意引用情况。对于指针类型参数,规则仍然适用,只不过延迟函数的参数是一个地址值,这种情况下,defer后面的语句对变量的修改会影响延迟函数。
多个defer,执行顺序
package main
import (
"fmt"
)
func main() {
defer fmt.Println("main defer1")
test()
defer fmt.Println("main defer2")
}
func test() () {
defer func() {
fmt.Println("test defer1")
}()
defer func() {
fmt.Println("test defer2")
}()
}
输出结果:
test defer2
test defer1
main defer2
main defer1
总结: 后进先出(LIFO)的顺序执行,即先出现的 defer 最后执行。即:多个defer语句的执行顺序是逆序执行。
defer的函数一定会执行么?
defer是Go语言中的延迟执行语句,用来添加函数结束时执行的代码,常用于释放某些已分配的资源、关闭数据库连接、断开socket连接、解锁一个加锁的资源。
Go语言机制担保一定会执行defer语句中的代码。其它语言中也有类似的机制,比如Java、C#语言里的finally语句,C++语言里的析构函数(Destructor)可以起类似的作用,C++语言机制担保在对象被销毁前一定会执行析构函数中的代码。C++中的析构函数析构的是对象,Go中的defer析构的是函数。
panic情况
网上demo:
package main
import (
"fmt"
)
func test1() {
fmt.Println("test")
}
func test2() {
panic(1)
}
func main() {
fmt.Println("main start")
defer test1()
test2() //造panic
fmt.Println("main end")
}
执行结果:
main start
test
panic: 1
goroutine 1 [running]:
main.test2(...)
总结: 我们发现正常的panic,还是会调我们的defer的,并且在会在panic之前执行。
os.Exit情况
网上demo:
package main
import (
"fmt"
"os"
)
func test1() {
fmt.Println("test")
}
func main() {
fmt.Println("main start")
defer test1()
fmt.Println("main end")
os.Exit(0)
}
执行结果:
main start
main end
总结: 如果在当前函数里是因为执行了os.Exit退出,而不是正常return退出或者panic退出,那程序会立即停止,被defer的函数调用不会执行。
kill情况(Ctrl+C)
package main
import (
"fmt"
"time"
)
func test1() {
fmt.Println("test")
}
func test2() {
time.Sleep(60 * time.Second)
}
func main() {
fmt.Println("main start")
defer test1()
test2()
fmt.Println("main end")
}
执行结果:
main start
Process finished with the exit code -1073741510 (0xC000013A: interrupted by Ctrl+C)
如上,我们test2() 睡眠时间内,点击Ctrl+C,发现defer test1()并没有执行。
总结:这个点很重要,需要我们在日常异常中断时,留意defer是否未处理的情况。
所以一般情况下,我们程序需要捕获这种异常中断,在程序退出前,手动做一些处理。
参考文献
Go常见坑:Go语言里被defer的函数一定会执行么?
参考URL: https://blog.csdn.net/perfumekristy/article/details/121343642
面试官:听说你精通golang的defer?
参考URL: https://cloud.tencent.com/developer/article/2076951
今天关于《golang中的defer函数理解》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于golang的内容请关注golang学习网公众号!
深入理解Golangchannel的应用
- 上一篇
- 深入理解Golangchannel的应用
- 下一篇
- Go语言k8s kubernetes使用leader election实现选举
-
- Golang · Go教程 | 2分钟前 |
- Golang多协程通信技巧分享
- 255浏览 收藏
-
- Golang · Go教程 | 22分钟前 |
- Golang如何判断变量类型?
- 393浏览 收藏
-
- Golang · Go教程 | 31分钟前 |
- Golang云原生微服务实战教程
- 310浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Golang迭代器与懒加载结合应用
- 110浏览 收藏
-
- Golang · Go教程 | 1小时前 | 性能优化 并发安全 Golangslicemap 预设容量 指针拷贝
- Golangslicemap优化技巧分享
- 412浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Golang代理模式与访问控制实现解析
- 423浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Golang事件管理模块实现教程
- 274浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- Golang接口多态实现全解析
- 241浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- GolangHTTP优化与中间件组合技巧
- 365浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3162次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3375次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3403次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4506次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3784次使用
-
- 一文搞懂Go语言中defer关键字的使用
- 2022-12-30 485浏览
-
- GolangDefer基础操作详解
- 2022-12-22 444浏览
-
- 详解golang中的闭包与defer
- 2023-01-07 461浏览
-
- go语言中的defer关键字
- 2023-02-17 150浏览
-
- Golang优雅保持main函数不退出的办法
- 2023-01-07 272浏览

