简单聊聊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 相关文章推荐
goland 清除所有的默认设置操作
Apr 28 Golang
golang 如何通过反射创建新对象
Apr 28 Golang
golang import自定义包方式
Apr 29 Golang
聊聊golang中多个defer的执行顺序
May 08 Golang
Golang实现AES对称加密的过程详解
May 20 Golang
再次探讨go实现无限 buffer 的 channel方法
Jun 13 Golang
Golang 并发下的问题定位及解决方案
Mar 16 Golang
golang为什么要统一错误处理
Apr 03 Golang
Golang原生rpc(rpc服务端源码解读)
Apr 07 Golang
golang三种设计模式之简单工厂、方法工厂和抽象工厂
Apr 10 Golang
Go语言 详解net的tcp服务
Apr 14 Golang
Golang入门之计时器
May 04 Golang
Go 中的空白标识符下划线
golang生成vcf通讯录格式文件详情
golang实现浏览器导出excel文件功能
Golang使用Panic与Recover进行错误捕获
Mar 22 #Golang
Go语言特点及基本数据类型使用详解
详解Golang如何优雅的终止一个服务
Mar 21 #Golang
Go语言实现一个简单的并发聊天室的项目实战
Mar 18 #Golang
You might like
Windows下PHP的任意文件执行漏洞
2006/10/09 PHP
php 什么是PEAR?(第二篇)
2009/03/19 PHP
php返回当前日期或者指定日期是周几
2015/05/21 PHP
php上传图片类及用法示例
2016/05/11 PHP
PHP设计模式之装饰器模式实例详解
2018/02/07 PHP
Yii2框架redis基本应用示例
2018/07/13 PHP
thinkphp框架实现路由重定义简化url访问地址的方法分析
2020/04/04 PHP
jquery.validate使用攻略 第五步 正则验证
2010/07/01 Javascript
Document.location.href和.replace的区别示例介绍
2014/03/04 Javascript
13个PHP函数超实用
2015/10/21 Javascript
谈一谈javascript中继承的多种方式
2016/02/19 Javascript
利用ES6实现单例模式及其应用详解
2017/12/09 Javascript
js图片查看器插件用法示例
2019/06/22 Javascript
python标准日志模块logging的使用方法
2013/11/01 Python
Python+django实现文件下载
2016/01/17 Python
浅谈scrapy 的基本命令介绍
2017/06/13 Python
使用requests库制作Python爬虫
2018/03/25 Python
Python函数装饰器实现方法详解
2018/12/22 Python
python3实现字符串操作的实例代码
2019/04/16 Python
怎么快速自学python
2020/06/22 Python
Pycharm安装第三方库失败解决方案
2020/11/17 Python
英国著名国际平价时尚男装品牌:Topman
2016/08/27 全球购物
Reebok官方旗舰店:美国知名健身品牌锐步
2019/01/07 全球购物
全球采购的街头服饰和帽子:Urban Excess
2020/10/28 全球购物
个人求职信范文分享
2014/01/06 职场文书
大二法学专业职业生涯规划范文
2014/02/12 职场文书
2014年重阳节活动策划方案书
2014/09/16 职场文书
生活小常识广播稿
2014/09/16 职场文书
2014业务员年终工作总结
2014/12/09 职场文书
三八节活动主持词
2015/07/04 职场文书
2016优秀大学生个人事迹材料范文
2016/03/01 职场文书
2019年英语版感谢信(8篇)
2019/09/29 职场文书
matplotlib之pyplot模块实现添加子图subplot的使用
2021/04/25 Python
python实现调用摄像头并拍照发邮箱
2021/04/27 Python
Java使用jmeter进行压力测试
2021/07/09 Java/Android
详解python的异常捕获
2022/03/03 Python