Golang——defer的学习及使用


Go提供了 defer关键字,可以注册多个延迟调用,这些调用以先进后出(FILO)的顺序在函数返回前被执行。

常用于保证一些资源最终一定能够得到回收和释放

一、defer后面必须是函数或方法的调用,不能是语句

必须满足该条件,否则会报expression in defer must be function call错误

还有个小细节就是:defer后面的函数是匿名函数时,一定要记得在结尾加(),表示调用该匿名函数,有参数的记得传参。否则也会报以上错误

二、defer函数的参数传递为值拷贝

defer函数的实参在注册时通过值拷贝传递进去,下面的实例代码中,实参a的值在 defer 注册时通过值拷贝传递进去,
后续语句 a++并不会影响defer语句最后的输出结果:

package main

import (
	"fmt"
)
// example1 defer函数的参数传递为值拷贝
func example() int {
	a := 0
	defer func(i int) {
		fmt.Println("defer i = ", i)
	}(a)
	// 不会影响 i 的输出
	a++
	return a
}

func main() {
	fmt.Println("结果为:", example())

}

输出:
defer i =  0
结果为: 1

三、defer语句必须先注册后执行

如果defer语句位于return之后,则defer因为没有注册,不会执行

// example2 defer语句必须先注册后执行
func example2() {
	defer func() {
		fmt.Println("First.....")
	}()
	a := 0
	fmt.Println("a----", a)
	return
	defer func() {
        // 不会执行
		fmt.Println("Second.....")
	}()
}
func main() {
	fmt.Println("defer语句必须先注册后执行 :")
	example2()
}

输出:
defer语句必须先注册后执行 :
a---- 0
First.....

四、主动调用os.Exit(int)退出进程时,defer将不再执行

即使defer已经提前注册了,仍不执行。

// example3 主动调用os.Exit(int)退出进程时,defer将不再执行
func example3() {
	defer func() {
		fmt.Println("defer不会被执行.....")
	}()
	fmt.Println("正常执行......")
	os.Exit(1)
}


func main() {
	fmt.Println("defer语句必须先注册后执行 :")
	example2()
}

输出:
主动调用os.Exit(int)退出进程时,defer将不再执行 :
正常执行......
exit status 1

五、多个需要关闭资源的场景,可以避免资源泄漏

defer 的好处是可以在一定程度上避免资源浪费,特别是在有很多return语句,有多个资源需要关闭的场景中,很容易漏掉资源的关闭操作,例如:

func example4(dst, src string) (w int64, err error) {
	fileSrc, err := os.Open(src)
	if err != nil {
		return
	}
	fileDst, err := os.Open(dst)
	if err != nil {
		// fileSrc很容易忘记关闭
		fileSrc.Close()
		return
	}
	w, err = io.Copy(fileDst, fileSrc)
	fileDst.Close()
	fileSrc.Close()
	return
}

使用defer改写后,在打开资源的无报错好直接调用defer关闭资源

func example4(dst, src string) (w int64, err error) {
	fileSrc, err := os.Open(src)
	if err != nil {
		return
	}
	fileDst, err := os.Open(dst)
	if err != nil {
		// fileSrc.Close()
		return
	}
	// 直接使用defer很方便,降低心智负担
	defer fileSrc.Close()
	w, err = io.Copy(fileDst, fileSrc)
	fileDst.Close()
	// fileSrc.Close()
	return
}

不过也要注意,defer语句的位置不当,有可能导致panic,一般defer语句放在错误检查语句之后。

六、副作用

defer也有明显的副作用:defer会推迟资源的释放,defer尽量不要放到循环语句里,将大函数内部的 defer语句单独拆分成一个小函数是一种很好的实践方式。

另外,defer相对于普通的函数调用需要间接的数据结构支持,相对于普通函数调用有一定的性能损耗

七、不要对有名返回值参数进行操作

defer中最要不要对有名返回值参数进行操作,否则会引发意想不到的结果

八、defer的执行顺序考察

package main

import "fmt"

func GetFn() func() {
	fmt.Println("outside")
	return func() {
		fmt.Println("inside")
	}
}
func main() {

	defer GetFn()()
	fmt.Println("main...")
}

错误输出结果:

main...
outside
inside

正确输出结果:

outside
main...
inside

解析:

GetFn() 整体可以看做是一个变量名,那么 GetFn()()可以表示为 F()

真正 defer 的是F()函数,对于F来讲,它只是一个字面量,也就是说,GetFn()还是正常执行,没有被defer。

因此在 main 函数里, 先执行GetFn() 然后F()被defer,所以正常执行main函数,打印 main…

此时 main 函数没有其他代码可执行了,因此就执行 defer 的代码 F(),就输出了 inside

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值