GO中DEFER的理解--DEFER执行的原理

在golang当中,defer代码块会在函数调用链表中增加一个函数调用。这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用。因此,defer通常用来释放函数内部变量。

为了更好的学习defer的行为,我们首先来看下面一段代码:

func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}

dst, err := os.Create(dstName)
if err != nil {
return
}

written, err = io.Copy(dst, src)
dst.Close()
src.Close()
return
}

  

这段代码可以运行,但存在‘安全隐患‘。如果调用dst, err := os.Create(dstName)失败,则函数会执行return退出运行。但之前创建的src(文件句柄)没有被释放。 上面这段代码很简单,所以我们可以一眼看出存在文件未被释放的问题。 如果我们的逻辑复杂或者代码调用过多时,这样的错误未必会被及时发现。 而使用defer则可以避免这种情况的发生,下面是使用defer的代码:

func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()

dst, err := os.Create(dstName)
if err != nil {
return
}
defer dst.Close()

return io.Copy(dst, src)
}

  

通过defer,我们可以在代码中优雅的关闭/清理代码中所使用的变量。defer作为golang清理变量的特性,有其独有且明确的行为。以下是defer三条使用规则。

规则一 当defer被声明时,其参数就会被实时解析

我们通过以下代码来解释这条规则:

func a() {
i := 0
defer fmt.Println(i)
i++
return
}

上面我们说过,defer函数会在return之后被调用。那么这段函数执行完之后,是不用应该输出1呢?

读者自行编译看一下,结果输出的是0. why?

这是因为虽然我们在defer后面定义的是一个带变量的函数: fmt.Println(i). 但这个变量(i)在defer被声明的时候,就已经确定其确定的值了。 换言之,上面的代码等同于下面的代码:

func a() {
i := 0
defer fmt.Println(0) //因为i=0,所以此时就明确告诉golang在程序退出时,执行输出0的操作
i++
return
}

为了更为明确的说明这个问题,我们继续定义一个defer:

func a() {
i := 0
defer fmt.Println(i) //输出0,因为i此时就是0
i++
defer fmt.Println(i) //输出1,因为i此时就是1
return
}

  

通过运行结果,可以看到defer输出的值,就是定义时的值。而不是defer真正执行时的变量值(很重要,搞不清楚的话就会产生于预期不一致的结果)

但为什么是先输出1,在输出0呢? 看下面的规则二。

规则二 defer执行顺序为先进后出

当同时定义了多个defer代码块时,golang安装先定义后执行的顺序依次调用defer。不要为什么,golang就是这么定义的。我们用下面的代码加深记忆和理解:

func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}

  

在循环中,依次定义了四个defer代码块。结合规则一,我们可以明确得知每个defer代码块应该输出什么值。 安装先进后出的原则,我们可以看到依次输出了3210.

规则三 defer可以读取有名返回值

先看下面的代码:

func c() (i int) {
defer func() { i++ }()
return 1
}

  

输出结果是12. 在开头的时候,我们说过defer是在return调用之后才执行的。 这里需要明确的是defer代码块的作用域仍然在函数之内,结合上面的函数也就是说,defer的作用域仍然在c函数之内。因此defer仍然可以读取c函数内的变量(如果无法读取函数内变量,那又如何进行变量清除呢....)。

当执行return 1 之后,i的值就是1. 此时此刻,defer代码块开始执行,对i进行自增操作。 因此输出2.

掌握了defer以上三条使用规则,那么当我们遇到defer代码块时,就可以明确得知defer的预期结果。

原文地址:https://www.cnblogs.com/ricklz/p/9574684.html

时间: 2024-10-07 06:12:28

GO中DEFER的理解--DEFER执行的原理的相关文章

go中defer的理解--defer、return、返回值之间执行顺序

defer可以读取有名返回值 func c() (i int) { defer func() { i++ }() return 1 } 输出结果是2. 在开头的时候,我们知道defer是在return调用之后才执行的. 这里需要明确的是defer代码块的作用域仍然在函数之内,结合上面的函数也就是说,defer的作用域仍然在c函数之内.因此defer仍然可以读取c函数内的变量(如果无法读取函数内变量,那又如何进行变量清除呢....). 当执行return 1 之后,i的值就是1. 此时此刻,def

理解Defer、Panic和Recover

刚开始的时候理解如何使用Defer和Recover有一点怪异,尤其是使用了try/catch块的时候.有一种模式可以在Go中实现和try/catch语句块一样的效果.不过之前你需要先领会Defer.Panic和Recover的精髓. 首先你需要理解defer关键字的作用,请看如下的代码: package main import ( "fmt" ) func main() { test() } func minicError(key string) error { return fmt.

script标签中的async和defer

在程序中代码是一行一行执行的,html标签都是由渲染引擎来执行,代码执行时从上往下一行一行执行,当执行到alert(如下图),alert会阻塞后面代码的执行,当点击完确定之后,代码继续往下执行. javascript的内容同样可在外部进行引用,如下图所示,正常情况下执行结果和上面的内容相同,但当我们给html的script标签中加入async或者defer属性时,代码的执行过程也将会随之改变. async 为异步,顾名思义就是多个人同时做多件事,这里区分sync,sync为同步,就是一个人有序的

java中return与finally的执行顺序

网上有很多人探讨Java中异常捕获机制try...catch...finally块中的finally语句是不是一定会被执行?很多人都说不是,当然他们的回答是正确的,经过我试验,至少有两种情况下finally语句是不会被执行的: (1)try语句没有被执行到,如在try语句之前就返回了,这样finally语句就不会执行,这也说明了finally语句被执行的必要而非充分条件是:相应的try语句一定被执行到. (2)在try块中有System.exit(0);这样的语句,System.exit(0);

转:Java中finally和return的执行关系

finally可以分两方面理解 1.执行时机问题.finally总会执行(除非是System.exit()),正常情况下在try后执行,抛异常时在catche后面执行 2.返回值问题.可以认为try(或者catch)中的return语句的返回值放入线程栈的顶部:如果返回值是基本类型则顶部存放的就是值,如果返回值是引用类型,则顶部存放的是引用.finally中的return语句可以修改引用所对应的对象,无法修改基本类型.但不管是基本类型还是引用类型,都可以被finally返回的“具体值”具体值覆盖

第二话:javascript中闭包的理解

闭包是什么? 通过闭包,子函数得以访问父函数的上下文环境,即使父函数已经结束执行. OK,我来简单叙述下,先上图. 都知道函数是javascript整个世界,对象是函数,方法是函数,并且js中实质性的面向对象相关也都是函数来实现和延伸,例如:"类". window:是指js中window类,也是js最高一层,因为什么这么说,因为你所有创建的方法和属性其实都在window之内.window中的所有方法,在自己创建的方法中都可以调到.可以仔细想想alert,在任何地方都可以alert,其实

Javascript中的EventLoop理解

原文来自 ruanyifeng.com 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Event Loop的理解. 上个月,我偶然看到了Philip Roberts的演讲<Help, I'm stuck in an event-loop>.这才尴尬地发现,自己的理解是错的.我决定重写这个题目,详细.完整.正确地描述JavaScript引擎的内部运行机制.下面就是我的重写. 进入正文之前,插播一条消息.我的新书<ECMAScript 6入门>出版了(版权页

JavaScript中this的理解

写在前面 这篇文章主要参考以下两篇文章学会js的this和js中this彻底理解 首先,本文讨论的情况都是在一班情况下,并非strict mode,下面进入正题,先熟悉几条原则,后面会有具体实例 几条原则 js中this在函数定义的时候是确定不了的,只有在函数运行的时候才能确定 如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window 如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象. 如果一个函数中有this,这个

JS中异常处理的理解

[转]JS中异常处理的理解 JS里的异常处理 JS的异常捕获与处理可以从它的 try-catch 语法结构说起,具体形式如下: try{ ... //异常的抛出 }catch(e){ ... //异常的捕获与处理 }finally{ ... //结束处理 } 其中,try块: try块包含的是可能产生异常的代码,在这里面直接或者在里面通过调用函数里间接抛出的异常都可以捕获到.部分浏览器还可以找到具体抛出的位置.详见e.stack. catch块: catch块,是捕获异常,并处理异常的地方,包括