zoukankan      html  css  js  c++  java
  • [Go] defer 语句

    Go 还有一些特有的流程控制语句,其中一个就是 defer 语句。该语句用于延迟调用指定的函数,它只能出现在函数的内部,由 defer 关键字以及针对某个函数的调用表达式组成。这里被调用的函数称为 延迟函数。一个简单的例子如下:

    func outerFunc()  {
    	defer fmt.Println("函数执行结束前一刻才会被打印")
    	fmt.Println("第一个被打印")
    }

    其中,defer 关键字后面是针对 fmt.Println 函数的调用表达式。代码里也说明了延迟函数的执行时机。这里的 outerFunc 称为 外围函数,调用 outerFunc 的那个函数称为 调用函数。下面是具体的规则:

    1. 当 外围函数 中的语句正常执行完毕时,只有其中所有的 延迟函数 都执行完毕,外围函数 才会真正结束执行。
    2. 当执行 外围函数 中的 return 语句时,只有其中所有的 延迟函数 都执行完毕后,外围函数 才会真正返回。
    3. 当 外围函数 中的代码引发运行时恐慌时,只有其中所有的 延迟函数 都执行完毕后,该运行时恐慌才会真正被扩散至调用函数。

    正因为 defer 语句有这样的特性,所有它成为了执行 释放资源 或 异常处理 等收尾任务的首选。明显的优势有 2 个,如下:

    1. 对 延迟函数 的调用总会在 外围函数 执行结束前执行。
    2. defer 语句在 外围函数 的函数体中的位置不限,并且数量不限。

    不过,使用 defer 语句还有 3 点需要注意:

    第一点:如果在 延迟函数 中使用外部变量,就应该通过参数传入,示例如下:

    func printNumbers()  {
    	for i := 0; i < 5; i++ {
    		defer func(){
    			fmt.Printf("%d", i)
    		}()
    	}
    }

    上述代码的执行结果为 55555,这正是由 延迟函数 的执行时机引起的。待那 5 个延迟函数执行时,它们使用的 i 值已经是 5 了。正确的做法是这样:

    func printNumbers()  {
    	for i := 0; i < 5; i++ {
    		defer func(n int){
    			fmt.Printf("%d", n)
    		}(i)
    	}
    }

    请注意,这时 延迟函数 有了参数,并且在调用它时也传入了参数值。如此一来,打印内容就会是 43210。为什么不是 01234 呢?请看下面的规则。

    第二点:同一个 外围函数 内多个 延迟函数 调用的执行顺序,会与其所属的 defer 语句的执行顺序 完全相反。你可以想象一下,同一个 外围函数 中每个 defer 语句在执行的时候,针对其 延迟函数 的调用表达式都会被压入同一个栈。在该 外围函数 执行结束的前一刻,Go 会从这个堆栈中依次取出并执行。

    第三点:延迟函数 调用若有参数传入,那么那些参数的值会在当前 defer 语句执行时求出。请看下面的示例:

    func printNumbers() {
    	for i := 0; i < 5; i++ {
    		defer func(n int) {
    			fmt.Printf("%d", n)
    		}(i * 2)
    	}
    }

    此时的执行结果是 86420。

    摘自:《Go 并发编程实战(第二版) . 郝林》

  • 相关阅读:
    java实现冒泡排序
    使用telnet测试指定端口的连通性
    聊聊Java中的异常及处理
    Java工具类——数学相关的类
    谈谈javaSE中的==和equals的联系与区别
    如何在Vim中更改颜色和主题
    配置类为什么要添加@Configuration注解呢?
    你知道Spring是怎么解析配置类的吗?
    读源码,我们可以从第一行读起
    Spring杂谈 | Spring中的AutowireCandidateResolver
  • 原文地址:https://www.cnblogs.com/52php/p/6714442.html
Copyright © 2011-2022 走看看