简单聊聊Golang中defer预计算参数


Posted in Golang onMarch 25, 2022

什么是defer

defer用来声明一个延迟函数,把这个函数放入到一个栈上, 当外部的包含方法return之前,返回参数到调用方法之前调用,也可以说是运行到最外层方法体的"}"时调用。我们经常用他来做一些资源的释放,比如关闭io操作

func doSomething(fileName string) {
    file,err := os.Open(fileName)
    if err != nil {
    panic(err)
    }
    defer file.Close()
}

defer 可以保证方法可以在外围函数返回之前调用。有点像其他言的 try finally

try{
}finally{
}

Go语言defer预计算参数

Go 语言中所有的函数调用都是传值的,虽然 defer 是关键字,但是也继承了这个特性。假设我们想要计算 main 函数运行的时间,可能会写出以下的代码:

package main
import (
	"fmt"
	"time"
)

func main() {
	startedAt := time.Now()
	defer fmt.Println(time.Since(startedAt))
	time.Sleep(time.Second) //休眠一秒
}

结果是:

D:\workspace\go\src\test>go run main.go
0s 

运行结果并不符合我们的预期,这个现象背后的原因是什么呢?经过分析,我们会发现调用 defer 关键字会立刻拷贝函数中引用的外部参数,所以 time.Since(startedAt) 的结果不是在 main 函数退出之前计算的,而是在 defer 关键字调用时计算的【defer入栈的时候】,最终导致上述代码输出 0s

我们再来看个简单例子来说明上述解释:

package main
import (
	"fmt"
)

func main() {
	i := 1
	defer fmt.Println(test(i))
	i = 100
}

func test(i int) int {
	i = i + 1
	return i
} 

D:\workspace\go\src\test>go run main.go
2

当代码运行到defer fmt.Println(test(i))的时候,会把defer右边最外层函数的参数计算完毕,并传递进函数里,但不会执行函数体的代码直到包裹defer的函数返回。我们先看会把defer右边最外层函数的参数计算完毕,并传递进函数里这句话,对应例子就是先把test(i)算出来,此时i=1,计算test(1)得2,然后fmt.Println(2)入栈,等到最后程序运行完了再运行defer结果就是2(但不会执行函数体的代码直到包裹defer的函数返回)。

我们再来看一个例子与匿名函数结合:

package main
import (
	"fmt"
)

func main() {
	i := 1
	defer func() {
		fmt.Println(test(i))
	}()
	i = 100
}

func test(i int) int {
	i = i + 1
	return i
}

结果:

D:\workspace\go\src\test>go run main.go
101  

使用匿名函数,结果是101,相当于i给到test方法的是100,那为什么呢?还是那句话:但不会执行函数体的代码直到包裹defer的函数返回

也就是说他会把整个{ fmt.Println(test(i)) }()函数体入栈,等到最后程序运行完了再运行defer,此时的i是100,运行test后就是101了。

所以你要解决第一个打印为0s的问题,你就可以使用匿名函数来解决,如下:

package main
import (
	"fmt"
	"time"
)

func main() {
	startedAt := time.Now()
	defer func() {
		fmt.Println(time.Since(startedAt))
	}()
	time.Sleep(time.Second) //休眠一秒
}

结果:

D:\workspace\go\src\test>go run main.go
1.0152825s

总结

到此这篇关于Golang中defer预计算参数的文章就介绍到这了,更多相关Go defer预计算参数内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Golang 相关文章推荐
Go语言使用select{}阻塞main函数介绍
Apr 25 Golang
golang 实现菜单树的生成方式
Apr 28 Golang
Go语言 go程释放操作(退出/销毁)
Apr 30 Golang
浅谈Golang 切片(slice)扩容机制的原理
Jun 09 Golang
Golang的继承模拟实例
Jun 30 Golang
go goroutine 怎样进行错误处理
Jul 16 Golang
使用GO语言实现Mysql数据库CURD的简单示例
Aug 07 Golang
一文搞懂Golang 时间和日期相关函数
Dec 06 Golang
golang操作rocketmq的示例代码
Apr 06 Golang
Go语言的协程上下文的几个方法和用法
Apr 11 Golang
golang连接MySQl使用sqlx库
Apr 14 Golang
golang语言指针操作
Apr 14 Golang
Go 中的空白标识符下划线
golang生成vcf通讯录格式文件详情
golang实现浏览器导出excel文件功能
Golang使用Panic与Recover进行错误捕获
Mar 22 #Golang
Go语言特点及基本数据类型使用详解
详解Golang如何优雅的终止一个服务
Mar 21 #Golang
Go语言实现一个简单的并发聊天室的项目实战
Mar 18 #Golang
You might like
法国:浪漫之都的咖啡文化
2021/03/03 咖啡文化
简单的js分页脚本
2009/05/21 Javascript
javascript实现的距离现在多长时间后的一个格式化的日期
2009/10/29 Javascript
javascript 多浏览器 事件大全
2010/03/23 Javascript
HTML5附件拖拽上传drop & google.gears实现代码
2011/04/28 Javascript
Javascript改变CSS样式(局部和全局)
2013/12/18 Javascript
javascript中的Base64、UTF8编码与解码详解
2015/03/18 Javascript
深入浅析JavaScript中prototype和proto的关系
2015/11/15 Javascript
js获取url传值的方法
2015/12/18 Javascript
JavaScript中函数声明与函数表达式的区别详解
2016/08/18 Javascript
Select2.js下拉框使用小结
2016/10/24 Javascript
js实现鼠标左右移动,图片也跟着移动效果
2017/01/25 Javascript
nodejs个人博客开发第七步 后台登陆
2017/04/12 NodeJs
使用nodejs爬取前程无忧前端技能排行
2017/05/06 NodeJs
jQuery中ajax获取数据赋值给页面的实例
2017/12/31 jQuery
vue 组件 全局注册和局部注册的实现
2018/02/28 Javascript
在 Typescript 中使用可被复用的 Vue Mixin功能
2018/04/17 Javascript
vue2.0结合Element-ui实战案例
2019/03/06 Javascript
JavaScript如何获取一个元素的样式信息
2019/07/29 Javascript
VUE注册全局组件和局部组件过程解析
2019/10/10 Javascript
vue中jsonp插件的使用方法示例
2020/09/10 Javascript
python创建和使用字典实例详解
2013/11/01 Python
python实现从web抓取文档的方法
2014/09/26 Python
Python中字符串的格式化方法小结
2016/05/03 Python
Python3操作SQL Server数据库(实例讲解)
2017/10/21 Python
用python求一重积分和二重积分的例子
2019/12/06 Python
Python如何使用正则表达式爬取京东商品信息
2020/06/01 Python
美国零售商店:Blue&Cream
2017/04/07 全球购物
Ibatis的核心配置文件都有什么
2014/09/08 面试题
初二政治教学反思
2014/01/12 职场文书
大学运动会通讯稿
2014/01/28 职场文书
水利水电专业自荐信
2014/07/08 职场文书
群众路线教育实践活动调研报告
2014/11/03 职场文书
2016年过年放假安排通知
2015/08/18 职场文书
公司转让协议书
2016/03/19 职场文书
我的收音机情缘
2022/04/05 无线电