Golang之defer 延迟调用操作
作者:程序猿编码 发布时间:2023-08-04 18:21:48
前言
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
}
以上为个人经验,希望能给大家一个参考
来源:https://blog.csdn.net/chen1415886044/article/details/105022265
猜你喜欢
- 电脑是64位的安装不了Windows (x86, 32-bit),Mysql installer MSI ,然后下载了Windo
- 原理经度 phi,纬度 theta 处的坐标为:x =R* cos(phi) * cos(theta)y = Rsin(phi) * cos
- 查看当前用户拥有的系统权限 select * from user_sys_privs; 系统权限 系统管理员授予-----sys用户 cre
- 下面就来介绍下SQL Server 2008中使用的端口有哪些:首先,最常用最常见的就是1433端口。这个是数据库引擎的端口,如果我们要远程
- 本文总结了一些简单基本的输出格式化形式,下面话不多说了,来看看详细的介绍吧。一、打印字符串>>> print "
- 1、安装 python3sudo apt install python32、卸载 python2.7 (可选)sudo apt remove
- js格式化金额,可选是否带千分位,可选保留精度,也是网上搜到的,但是使用没问题 /* 将数值四舍五入后格式化. @param num 数值(
- 本文介绍了python opencv之SURF算法示例,分享给大家,具体如下:目标:SURF算法基础opencv总SURF算法的使用原理:上
- 本文主要研究的是flask如何截获所有访问,以及before_request、after_request修饰器的相关内容,具体如下。在学习着
- 下学期就要学习MySQL了,没事先在家搞一搞,没想到光安装就费了半天劲,所以我决定整理下,供大家参考。第一步 下载安装包:官网毕竟是甲骨文公
- 前言大家应该都有所体会,为了提高验证码的识别准确率,我们当然要首先得到足够多的测试数据。验证码下载下来容易,但是需要人脑手工识别着实让人受不
- iUI、jQTouch、WPTouch、PhoneGap、XUI、iWebkit、Rhodes、gwt-mobile…当我们已经开始惊叹 w
- XPath 的安装以及使用1 . XPath 的介绍刚学过正则表达式,用的正顺手,现在就把正则表达式替换掉,使用 XPath,有人表示这太坑
- 前言:我的python学习也告一段落了。不过有些,方法还是打算总结一下和大家分享。我整理了使用matplotlib绘制折线图的一般步骤,按照
- 一、事件捕捉(Event Capture)的实现问题首先在说这件事前,先感谢一下Realazy。 W3C DOM Level2的事
- 前言在使用爬虫的时候,很多网站都有一定的反爬措施,甚至在爬取大量的数据或者频繁地访问该网站多次时还可能面临ip被禁,所以这个时候我们通常就可
- 如何对设计进行评判,一定有很多答案,有利有弊。问100位设计师,会得到100种回答。用线上PV、UV等数据说话,更多受产品属性、运营动作影响
- 由于XML本身的诸多优点,XML技术已被广泛的使用,目前的好多软件技术同XML紧密相关,比如微软的.net 平台对xml提供了强大的支持,提
- 新装MySQL后,首次执行 mysql -uroot -p 后会发现root密码不为空,要重置root密码请参考以下步骤。编辑mysql配置
- 1.常用数据结构之列表我们先给大家一个编程任务,将一颗色子掷6000次,统计每个点数出现的次数。这个任务对大家来说应该是非常简单的,我们可以