Golang之defer 延迟调用操作
来源:脚本之家
时间:2022-12-27 08:13:11 383浏览 收藏
来到golang学习网的大家,相信都是编程学习爱好者,希望在这里学习Golang相关编程知识。下面本篇文章就来带大家聊聊《Golang之defer 延迟调用操作》,介绍一下defer、延迟,希望对大家的知识积累有所帮助,助力实战开发!
前言
defer语句被用于预定对一个函数的调用。我们把这类被defer语句调用的函数称为延迟函数。而defer 延迟语句在其他编程语言里好像没有见到。应该是属于 Go 语言里的独有的关键字。但用法类似于面向对象编程语言 Java 和 C# 的 finally 语句块。
下面对defer进行介绍。
defer特性
1. 关键字 defer 用于注册延迟调用。
2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。
3. 多个defer语句,按先进后出的方式执行。
1.延迟调用
用法很简单,只需要在函数前面加上 defer就行,就能实现将这个 该函数的调用延迟到当前函数执行完后再执行。例如:
package main import ( "fmt" ) func myFunc(){ fmt.Println("minger") } func main(){ defer myFunc() //等价于defer fmt.Println("minger") fmt.Println("程序猿编码") }
编译运行:
2.defer 与 return 孰先孰后
defer 和 return 到底是哪个先调用?先看看例子:
package main import ( "fmt" ) var name string = "go" func myFunc() string { defer func() { name = "python" }() fmt.Println("myFunc 函数里的name:", name) return name } func main() { myName := myFunc() fmt.Println("main 函数里的name: ", name) fmt.Println("main 函数里的myname: ", myName )
编译运行:
来看看打印信息,第一行输出,name 此时还是全局变量,值还是go
第二行输出,在 defer 里改变了全局变量,此时name的值已经变成了 python
重点在第三行,为什么输出的是 go ?
解释只有一个,那就是 defer 是return 后才调用的。所以在执行 defer 前,myName 已经被赋值成 go 了。
3.多个defer 逆序执行
还是老规矩先来上代码,看看输出信息,例子:
package main import ( "fmt" ) func main(){ name := "go" defer fmt.Println(name) name = "C/C++" defer fmt.Println(name) name = "Python" fmt.Println(name) }
编译输出:
可见 多个defer 是它们会以逆序执行(类似栈,即后进先出)。
defer官方的解释
Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the “defer” statement is executed.
翻译一下:
每次defer语句执行的时候,会把函数“压栈”,函数参数会被拷贝下来;当外层函数(非代码块,如一个for循环)退出时,defer函数按照定义的逆序执行;如果defer执行的函数为nil, 那么会在最终调用函数的产生panic.
为什么需要defer?
往往我们在编程的时候,经常需要打开一些资源,比如数据库连接、文件、锁等,这些资源需要在用完之后释放掉,否则会造成内存泄漏。
因此我们有时会忘记关闭这些资源。Golang直接在语言层面提供defer关键字,在打开资源语句的下一行,就可以直接用defer语句来注册函数结束后执行关闭资源的操作。
defer用途
1. 关闭文件句柄
2. 锁资源释放
3. 数据库连接释放
defer的使用其实非常简单,来看看一个简单用途:
package main import ( "log" "os" ) func main() { f, err := os.OpenFile("text.txt", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) //文件没有就创建,文件存在就追加 if err != nil { log.Fatal(err) } defer f.Close() f.WriteString("程序猿编码\n") }
编译输出:
在打开文件的语句附近,用defer语句关闭文件。这样,在函数结束之前,会自动执行defer后面的语句来关闭文件。
当然,defer会有小小地延迟,对时间要求特别特别特别高的程序,可以避免使用它。
总结
defer 语句经常使用于成对的操作,比如打开和关闭,连接和断开,加锁和解锁,即便是再复杂的控制流,资源在任何情况下都能够正确释放。
补充:Golang中defer的三个实战要点
前言
Golang中的defer是使用频次比较高的,能创造出延迟生效特效的一种方式。
defer也有自己的矫情,需要注意的。
本文将从通过代码的方式来说明defer的三点矫情。
1.defer的生效顺序
2.defer与return,函数返回值之间的顺序
3.defer定义和执行两个步骤,做的事情。
正文
1.defer的生效顺序
先说结论:defer的执行顺序是倒序执行(同入栈先进后出)
func main() { defer func() { fmt.Println("我后出来") }() defer func() { fmt.Println("我先出来") }() }
执行后打印出:
我先出来
我后出来
2.defer与return,函数返回值之间的顺序
先说结论:return最先执行->return负责将结果写入返回值中->接着defer开始执行一些收尾工作->最后函数携带当前返回值退出
返回值的表达方式,我们知道根据是否提前声明有两种方式:一种是func test() int 另一种是 func test() (i int),所以两种情况都来说说
func test() int func main() { fmt.Println("main:", test()) } func test() int { var i int defer func() { i++ fmt.Println("defer2的值:", i) }() defer func() { i++ fmt.Println("defer1的值:", i) }() return i }
输出:
defer1的值: 1
defer2的值: 2
main: 0
详解:return的时候已经先将返回值给定义下来了,就是0,由于i是在函数内部声明所以即使在defer中进行了++操作,也不会影响return的时候做的决定。
func test() (i int) func main() { fmt.Println("main:", test()) } func test() (i int) { defer func() { i++ fmt.Println("defer2的值:", i) }() defer func() { i++ fmt.Println("defer1的值:", i) }() return i }
输出:
defer1的值: 1
defer2的值: 2
main: 2
详解:由于返回值提前声明了,所以在return的时候决定的返回值还是0,但是后面两个defer执行后进行了两次++,将i的值变为2,待defer执行完后,函数将i值进行了返回。
3.defer定义和执行两个步骤,做的事情
先说结论:会先将defer后函数的参数部分的值(或者地址)给先下来【你可以理解为()里头的会先确定】,后面函数执行完,才会执行defer后函数的{}中的逻辑
func test(i *int) int { return *i } func main(){ var i = 1 // defer定义的时候test(&i)的值就已经定了,是1,后面就不会变了 defer fmt.Println("i1 =" , test(&i)) i++ // defer定义的时候test(&i)的值就已经定了,是2,后面就不会变了 defer fmt.Println("i2 =" , test(&i)) // defer定义的时候,i就已经确定了是一个指针类型,地址上的值变了,这里跟着变 defer func(i *int) { fmt.Println("i3 =" , *i) }(&i) // defer定义的时候i的值就已经定了,是2,后面就不会变了 defer func(i int) { //defer 在定义的时候就定了 fmt.Println("i4 =" , i) }(i) defer func() { // 地址,所以后续跟着变 var c = &i fmt.Println("i5 =" , *c) }() // 执行了 i=11 后才调用,此时i值已是11 defer func() { fmt.Println("i6 =" , i) }() i = 11 }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持golang学习网。如有错误或未考虑完全的地方,望不吝赐教。
今天关于《Golang之defer 延迟调用操作》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!
-
366 收藏
-
485 收藏
-
444 收藏
-
321 收藏
-
426 收藏
-
441 收藏
-
424 收藏
-
164 收藏
-
266 收藏
-
444 收藏
-
311 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习