简单聊聊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缓冲channel和非缓冲channel的区别说明
Apr 25 Golang
Go语言 go程释放操作(退出/销毁)
Apr 30 Golang
解决golang结构体tag编译错误的问题
May 02 Golang
Golang 编译成DLL文件的操作
May 06 Golang
Go语言应该什么情况使用指针
Jul 25 Golang
golang内置函数len的小技巧
Jul 25 Golang
golang实现浏览器导出excel文件功能
Mar 25 Golang
Golang 链表的学习和使用
Apr 19 Golang
Golang jwt身份认证
Apr 20 Golang
Go Grpc Gateway兼容HTTP协议文档自动生成网关
Jun 16 Golang
Go结合Gin导出Mysql数据到Excel表格
Aug 05 Golang
Go 中的空白标识符下划线
golang生成vcf通讯录格式文件详情
golang实现浏览器导出excel文件功能
Golang使用Panic与Recover进行错误捕获
Mar 22 #Golang
Go语言特点及基本数据类型使用详解
详解Golang如何优雅的终止一个服务
Mar 21 #Golang
Go语言实现一个简单的并发聊天室的项目实战
Mar 18 #Golang
You might like
实用函数4
2007/11/08 PHP
php面向对象全攻略 (五) 封装性
2009/09/30 PHP
PHP clearstatcache()函数详解
2010/03/02 PHP
ThinkPHP空模块和空操作详解
2014/06/30 PHP
帝国cms常用标签汇总
2015/07/06 PHP
php+ajax实现无刷新文件上传功能(ajaxuploadfile)
2018/02/11 PHP
PHP PDOStatement::bindValue讲解
2019/01/30 PHP
解决laravel查询构造器中的别名问题
2019/10/17 PHP
JavaScript中的style.display属性操作
2013/03/27 Javascript
jquery如何扑捉回车键触发的事件
2014/04/24 Javascript
javascript检查浏览器是否已经启用XX功能
2015/07/10 Javascript
json定义及jquery操作json的方法
2016/09/29 Javascript
JS实用的带停顿的逐行文本循环滚动效果实例
2016/11/23 Javascript
JS实现为动态添加的元素增加事件功能示例【基于事件委托】
2018/03/21 Javascript
JS中验证整数和小数的正则表达式
2018/10/08 Javascript
微信小程序实现转盘抽奖
2020/09/21 Javascript
利用JavaScript模拟京东按键输入功能
2020/12/01 Javascript
[01:11:10]2014 DOTA2华西杯精英邀请赛 5 24 iG VS VG加赛
2014/05/26 DOTA
Ubuntu下安装PyV8
2016/03/13 Python
Python环境下安装使用异步任务队列包Celery的基础教程
2016/05/07 Python
python paramiko模块学习分享
2017/08/23 Python
完美解决python中ndarray 默认用科学计数法显示的问题
2018/07/14 Python
python实践项目之监控当前联网状态详情
2019/05/23 Python
浅谈django2.0 ForeignKey参数的变化
2019/08/06 Python
Python集成开发工具Pycharm的安装和使用详解
2020/03/18 Python
五分钟学会怎么用Pygame做一个简单的贪吃蛇
2021/01/06 Python
【HTML5】Canvas绘制简单图片教程
2016/05/13 HTML / CSS
HTML块级标签汇总(小篇)
2016/07/13 HTML / CSS
实习生自我鉴定范文
2013/12/05 职场文书
成功的餐厅经营创业计划书
2014/01/15 职场文书
点菜员岗位职责范本
2014/02/14 职场文书
函授毕业生自我鉴定范文
2014/03/25 职场文书
滴水洞导游词
2015/02/10 职场文书
2015年度党员个人总结
2015/02/14 职场文书
Java数组与堆栈相关知识总结
2021/06/29 Java/Android
使用GO语言实现Mysql数据库CURD的简单示例
2021/08/07 Golang