golang defer的使用
在golang当中,defer代码块会在函数调用链表中增加一个函数调用。这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用。因此,defer通常用来释放函数内部变量。
通过defer,我们可以在代码中优雅的关闭/清理代码中所使用的变量。defer作为golang清理变量的特性,有其独有且明确的行为。以下是defer三条使用规则。
规则一 当defer被声明时,其参数就会被实时解析
我们通过以下代码来解释这条规则:
func a() {i := 0defer fmt.Println(i)i++return
}
运行结果是0
这是因为虽然我们在defer后面定义的是一个带变量的函数: fmt.Println(i). 但这个变量(i)在defer被声明的时候,就已经确定其确定的值了。 换言之,上面的代码等同于下面的代码:
func a() {i := 0defer fmt.Println(0) //因为i=0,所以此时就明确告诉golang在程序退出时,执行输出0的操作i++return
}
为了更为明确的说明这个问题,我们继续定义一个defer:
func a() {i := 0defer fmt.Println(i) //输出0,因为i此时就是0i++defer fmt.Println(i) //输出1,因为i此时就是1return
}
通过运行结果,可以看到defer输出的值,就是定义时的值。而不是defer真正执行时的变量值(很重要,搞不清楚的话就会产生于预期不一致的结果)
再看一个例子:
package mainimport "fmt"
func f1() (result int) {defer func() {result++}()return 0
}func f2() (r int) {t := 5defer func() {t = t+5}()return t
}func f3() (t int) {t = 5defer func() {t = t+5}()return t
}
func f4() (r int) {defer func(r int) {r = r + 5}(r)return 1
}func main() {fmt.Println(f1())fmt.Println(f2())fmt.Println(f3())fmt.Println(f4())
}
运行结果是:
1
5
10
1
函数返回的过程是这样子的:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。
defer表达式可能会在设置函数返回值之后,在返回到调用函数之前,修改返回值,使最终的函数返回值与你想象的不一致。
可以将return xxx改成
返回值=xxx
调用defer函数
空的return
那上面的例子就可以改成:
func f11() (result int) {result = 0 //先给返回值赋值func(){ //再执行defer 函数result++}()return //最后返回
}func f22() (r int) {t := 5r = t //赋值指令func() { //defer 函数被插入到赋值与返回之间执行,这个例子中返回值r没有被修改t = t+5}return //返回
}func f33() (t int) {t = 5 //赋值指令func(){t = t+5 //然后执行defer函数,t值被修改}return
}
func f44() (r int) {r = 1 //给返回值赋值func(r int){ //这里的r传值进去的,是原来r的copy,不会改变要返回的那个r值r = r+5}(r)return
}
规则二 defer执行顺序为先进后出
当同时定义了多个defer代码块时,golang安装先定义后执行的顺序依次调用defer。不要为什么,golang就是这么定义的。我们用下面的代码加深记忆和理解:
func b() {for i := 0; i < 4; i++ {defer fmt.Print(i)}
}
在循环中,依次定义了四个defer代码块。结合规则一,我们可以明确得知每个defer代码块应该输出什么值。 安装先进后出的原则,我们可以看到依次输出了3210.
再看之前的那个例子:
package mainimport "fmt"func main() {fmt.Println("a return:", a()) // 打印结果为 a return: 0
}func a() int {var i intdefer func() {i++fmt.Println("a defer2:", i) // 打印结果为 a defer2: 2}()defer func() {i++fmt.Println("a defer1:", i) // 打印结果为 a defer1: 1}()return i
}
结果是:
a defer1: 1
a defer2: 2
a return: 0
规则三 defer可以读取有名返回值
先看下面的代码:
func c() (i int) {defer func() { i++ }()return 1
}
输出结果是12. 在开头的时候,我们说过defer是在return调用之后才执行的。 这里需要明确的是defer代码块的作用域仍然在函数之内,结合上面的函数也就是说,defer的作用域仍然在c函数之内。因此defer仍然可以读取c函数内的变量(如果无法读取函数内变量,那又如何进行变量清除呢…)。
当执行return 1 之后,i的值就是1. 此时此刻,defer代码块开始执行,对i进行自增操作。 因此输出2.
看这个例子:
package mainimport "fmt"
func trace(s string) string {fmt.Println("entering:",s)return s
}func un(s string) {fmt.Println("leaving:",s)
}func a() {defer un(trace("a"))fmt.Println("in a")
}func b() {defer un(trace("b"))fmt.Println("in b")a()
}func main() {b()
}
运行结果是:
entering: b
in b
entering: a
in a
leaving: a
leaving: b
golang defer的使用相关推荐
- golang defer实现
golang defer实现 1.现象 defer 会在函数return前执行 如果发生非系统级别panic,defer依然执行:在defer执行过程中,如果有recover,那就捕获panic,否则 ...
- golang defer简介 goland 警告提示 possible resource leak,difer is called in a for loop 原因
目录 警告原因 解决方法 defer理解 defer调用是一个栈结构 defer的作用域是一个函数,不是一个语句块 链式调用 针对非指针类型调用函数 警告原因 在for中使用defer关闭资源,其实资 ...
- Golang defer 快速上手
文章目录 1.简介 2.注意事项 2.1 defer 函数入参在 defer 时确定 2.2 defer 执行顺序为后进先出 2.3 defer 函数在 return 语句赋值与返回之间执行 2.4 ...
- Golang defer
在Golang使用defer常常会迷惑于以下两个问题 defer 关键字的调用时机以及多次调用 defer 时执行顺序是如何确定的: defer 关键字使用传值的方式传递参数时会进行预计算,导致不符合 ...
- Golang defer解读
defer defer是Go语言提供的一种用于注册延迟调用的机制:让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行. defer语句通常用于一些成对 ...
- golang defer 关闭文件 报错file may have nil or other unexpected value as its corresponding error
错误实例: file, err := os.Open("xxx.txt") defer file.Close() if err != nil {return err } 初学者很多 ...
- golang defer使用——资源关闭时候多用
defer Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句.当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回.特别是当你在进行一些打开资源 ...
- golang——defer
defer是什么 defer是Go语言提供的一种用于注册延迟调用的机制,让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行. defer与panic和 ...
- [Golang]defer详解
数据结构 defer的数据结构定义在$GOROOT/src/runtime/runtime2.go // 大体定义如下,忽略少部分字段 type _defer struct {sp uintptr / ...
最新文章
- SAP S/4HANA BP功能
- Windows Server 2008 R2 如何显示被隐藏的文件扩展名
- HTML5实例教程:OL标签的start属性和reversed属性
- 数据结构源码笔记(C语言):直接选择排序
- Spring事务的实现方式和实现原理
- java使用:: 表达式_Java 12:开关表达式
- 拆分-洛谷P2745 [USACO5.3]窗体面积Window Area
- php清理html table样式,Parse HTML Table - PHP [closed]
- 【Win10】【Win2D】实现控件阴影效果
- 上海飞国内最远是哪里_讯飞连发三款智能录音笔!可离线转写拍视频秒配字幕,首推智能TWS耳机...
- 使用R包GD实现地理探测器算法
- VMware安装Windows XP虚拟机并手动安装外加驱动程序
- 在龙芯3A3000中标麒麟7.0环境下编译golang1.14.1源码
- 野火指南者(STM32F103)移植LVGL
- 软件工程实训项目(一)——IDEA连接Mysql数据库
- MongoDB和MySQL常用增删改查语句
- 优雅地寻找网站源码(一)
- 复制粘贴Excel文件后,显示“安全警告 宏已被禁用”,复制后得到的文件看不到内容
- Java斗_Java集合练习:斗地主游戏
- 【洛谷P5514】永夜的报应【模拟】