博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
golang中的defer语句你真的会吗?
阅读量:3986 次
发布时间:2019-05-24

本文共 2805 字,大约阅读时间需要 9 分钟。

1、defer的作用

defer后面的函数在defer语句所在的函数执行结束的时候会被调用,用来做一些收尾工作,当然它比面向对象语言中的析构函数强大,因为它还有其他的任务,比如,异常捕获,资源释放,修改函数返回值。

defer在实际应用中非常常见,因此需要对其有一个透彻的理解。

defer后面必须是函数调用语句,不能是其他语句,否则编译器会出错。

2、defer的三条行为规则
2.1 规则一:延迟函数的参数在defer语句出现时就已经确定下来了
func a() {
i := 0 defer fmt.Println(i) i++ return}

defer语句中的fmt.Println()的参数i值在defer出现时就已经确定下来,实际上是拷贝了一份。

后面对变量i的修改不会影响fmt.Println()函数的执行,仍然打印 0。

再比如:

func tt2() {
a := []int{
1, 2} defer func(x []int){
fmt.Println(x) // [1 2] 复制了切片 }(a) a = append(a, 3, 4, 5)}

对于指针类型参数,规则仍然适用,只不过延迟函数的参数是一个地址值,这种情况下,如果指针指向的变量值发生了变化,那么会影响到延迟函数的指向结果。

func tt3() {
a := &Person{
"Rao", 12} defer func(p *Person){
fmt.Println(p.Name) // xiao }(a) a.Name = "xiao"}

由以上例子可知,defer语句在定义的时候会复制实参的值到临时变量,临时变量的值就此固定下来,要么是值要么是指针。

注意这里说的是延迟函数的参数,比如:

defer func(x int) {
fmt.Println(x)}(i)

如果延迟函数不需要传参呢:

defer func() {
fmt.Println(i)}()

此时的变量 i 是没有固定下来的。

如果延迟函数的参数是另一个函数 ff 的返回值,那么在定义 defer 语句的时候,ff 函数会被执行,以拿到返回值,确定延迟函数的额参数。

2.2 规则二:延迟函数执行按后进先出顺序执行,即先出现的defer最后执行

这个规则很好理解,定义defer类似于入栈操作,执行defer类似于出栈操作。

设计defer的初衷是简化函数返回时资源清理的动作,资源往往有依赖顺序,比如先申请A资源,再跟据A资源申请B资源,跟据B资源申请C资源,即申请顺序是:A–>B–>C,释放时往往又要反向进行。这就是把deffer设计成FIFO的原因。

每申请到一个用完需要释放的资源时,立即定义一个defer来释放资源是个很好的习惯。

2.3 规则三:延迟函数可能操作主函数的具名返回值

定义defer的函数,即主函数可能有返回值,返回值有没有名字没有关系,defer所作用的函数,即延迟函数可能会影响到返回值。

若要理解延迟函数是如何影响主函数返回值的,只要明白函数是如何返回的就足够了。

有一个事实必须要了解,关键字return不是一个原子操作,实际上return只代理汇编指令ret,即将跳转程序执行。

比如语句return i,实际上分两步进行,即将i值存入栈中作为返回值,然后执行跳转,而defer的执行时机正是跳转前,所以说defer执行时还是有机会操作返回值的。

举个实际的例子进行说明这个过程:

func deferFuncReturn() (result int) {
i := 1 defer func() {
result++ }() return i}

返回值为 2

return语句执行过程:

将 i 赋值给 result;
然后执行 defer 函数;
result++

再比如:

func tt5() int {
i := 1 defer func() {
i++ }() return i}

返回值为 1

return语句执行过程:

将 i 赋值给 临时变量temp;
然后执行 defer 函数;
i++

关于主函数有不同的返回方式,但返回机制就如上面介绍所说,只要把return语句拆开都可以很好的理解。

同样的,对于指针变量,也可以这样分析。

func tt6() *Person {
a := &Person{
"Rao", 12} defer func(p *Person){
p.Name = "xiao" }(a) return a}

返回值 &{xiao 12}

3、defer使用注意

defer后面的函数在defer语句所在的 函数执行结束 并且 函数返回前 的时候会被调用。

设想有一个需要长期运行的函数,其中有无限循环语句,在循环体内不断的创建资源(或分配内存),并用defer语句确保释放。由于函数一直运行没有返回,所有defer语句都得不到执行,循环过程中创建的大量短暂性资源一直积累着,得不到回收。而且,系统为了存储defer列表还要额外占用资源,也是持续增加的。这样下去,过不了多久,整个系统就要因为资源耗尽而崩溃。

例如:

func tt7() {
for {
f, err := os.Open(...) defer f.Close() ... }}

由于 tt7 函数永远不会返回,所以 defer 语句永远不会被执行到。

针对这种情况,需要做一点优化,使系统资源得到释放,在defer外层套一个闭包函数就可以了,如下:

func tt7() {
for {
func(){
f, err := os.Open(...) defer f.Close() ... }() }}
4、defer 与 panic

一旦 defer 注册成功,那么后面即使有 panic 也不会影响其执行。

func main() {
re := ttss() fmt.Println(re)}func ttss() (result bool) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err) } }() result = tts() return result}func tts() (result bool) {
panic("xxx") return true}

打印

xxxfalse

转载地址:http://vjaui.baihongyu.com/

你可能感兴趣的文章
CCF 分蛋糕
查看>>
解决python2.7中UnicodeEncodeError
查看>>
小谈python 输出
查看>>
Django objects.all()、objects.get()与objects.filter()之间的区别介绍
查看>>
python:如何将excel文件转化成CSV格式
查看>>
Django 的Error: [Errno 10013]错误
查看>>
机器学习实战之决策树(一)
查看>>
[LeetCode By Python] 2 Add Two Number
查看>>
python 中的 if __name__=='__main__' 作用
查看>>
机器学习实战之决策树二
查看>>
[LeetCode By Python]7 Reverse Integer
查看>>
[LeetCode By Python]9. Palindrome Number
查看>>
[LeetCode By Python]13 Roman to Integer
查看>>
[leetCode By Python] 14. Longest Common Prefix
查看>>
[LeetCode By Python]107. Binary Tree Level Order Traversal II
查看>>
[LeetCode By Python]108. Convert Sorted Array to Binary Search Tree
查看>>
[leetCode By Python]111. Minimum Depth of Binary Tree
查看>>
[LeetCode By Python]112. Path Sum
查看>>
[LeetCode By Python]118. Pascal's Triangle
查看>>
[LeetCode By Python]119. Pascal's Triangle II
查看>>