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
猜你喜欢
- 在Oracle数据库中,DBA可以通过观测一定的表或视图来了解当前空间的使用状况,进而作出可能的调整决定。 一.表空间的自由空间 通过对表空
- MySQL是一个跨平台的开源关系型数据库管理系统,是我们常用的最经济实惠的数据库,由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特
- jsp登陆验证,网页登陆验证带验证码校验,登录功能之添加验证码part_1:专门用于生成一个验证码图片的类:VerificationCode
- parse_dates : boolean or list of ints or names or list of lists or dic
- 在html中关于select元素的问题在很多地方都提出过,而在前段时间的项目中,刚好遇到了关于select元素的两个小问题,这里进行一下总结
- 这可是个综合性的问题,看看下面对文件操作的集大成代码:<% 'Set file i/
- golang中GOPATH的简单理解 1、为什么要配置GOPATH配置GOPATH的用意是为了方便项目的部署和构建,以及可以直接使用go g
- 用两个文件.GLOBAL.ASA和online.asp下面分别给出两个文件的源代码.呵呵,我也是菜鸟,大家加油哟!<SCR
- 今天彬Go将和大家一起讨论网页设计趋势中很重要的环节,那就是”勾引”用户的按钮。所谓”勾引”用户的按钮,其实对于Web设计师来说,就是如何设
- 我们首先来看下实例代码:function sig_handler($sig){ print("handled sig:
- 在Vista IIS 7 中用 vs2005 调试 Web 项目核心是要解决以下几个问题:1、Vista 自身在安全性方面的User Acc
- 由于代码比较短,这里就不进行注释了代码如下:<% '当目标页面的包含文件即#include的页面里边存在respon
- PDOStatement::nextRowsetPDOStatement::nextRowset — 在一个多行集语句句柄中推进到下一个行集
- sql server中变量要先申明后赋值:局部变量用一个@标识,全局变量用两个@(常用的全局变量一般都是已经定义好的);申明局部变量语法:d
- 问:握怎样测试 MySQL安装?答:可以通过以下命令测试MySQL服务器是否工作:C:\> C:\Program Files\MySQ
- 问题1:使用.net2005自带的SQL-Express连接不上。解决方法:1.网络防火墙阻止数据库连接;2.默认SQL-Express没有
- 在网页设计中有一些通用的交互设计模式。网站导航各种各样的通用和大家熟知的设计模式,可以用来作为为网站创建有效地信息架构的基础。这篇指南涵盖了
- 这周心血来潮,翻看了现在比较流行的几个JS脚本框架的底层代码,虽然是走马观花,但也受益良多,感叹先人们的伟大……感叹是为了缓解严肃的气氛并引
- 上一篇:微软建议的ASP性能优化28条守则(6) 技巧 20:避免在循环语句中使用字符串串联 许多人在循环语句中建立一个字符串,如下所示:
- 第一节:WAP的潜能 这些日子,我们常听到WAP技术,一种手机上网的技术。从技术上讲,移动电话不可能和PC来竞争,移动电话的屏幕只能容下很少