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 并发编程实战(第二版) . 郝林》

[Go] defer 语句的更多相关文章

  1. go语言之goto语句和函数和defer语句

    1.goto关键字 import "fmt" func main() { for i := 0;i <11;i++{ if i == 2{ //关键字,goto跳转到某个位置 ...

  2. golang学习 ---defer语句

    golang语言defer特性详解 defer语句是go语言提供的一种用于注册延迟调用的机制,它可以让函数在当前函数执行完毕后执行,是go语言中一种很有用的特性.由于它使用起来简单又方便,所以深得go ...

  3. go语言的defer语句

    转: https://www.jianshu.com/p/5b0b36f398a2 ---------------------------------------------------------- ...

  4. Go语言中defer语句使用小结

    defer是Go语言中的延迟执行语句,用来添加函数结束时执行的代码,常用于释放某些已分配的资源.关闭数据库连接.断开socket连接.解锁一个加锁的资源.Go语言机制担保一定会执行defer语句中的代 ...

  5. 探究 Go 语言 defer 语句的三种机制

    Golang 的 1.13 版本 与 1.14 版本对 defer 进行了两次优化,使得 defer 的性能开销在大部分场景下都得到大幅降低,其中到底经历了什么原理? 这是因为这两个版本对 defer ...

  6. Go语言学习——函数二 defer语句

    函数 package main import "fmt" // 函数:一段代码的封装 func f1(){ fmt.Println("Hello 中国!") } ...

  7. go defer 语句会延迟函数的执行直到上层函数返回。

    defer code... 可以理解为 执行完当前defer所在的方法代码后执行defer 中的代码 常用在释放资源 比如 关闭文件 为防止忘记编写关闭代码 可以先写好   defer  各种释放资源 ...

  8. 【Go入门教程3】流程(if、goto、for、switch)和函数(多个返回值、变参、传值与传指针、defer、函数作为值/类型、Panic和Recover、main函数和init函数、import)

    这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和 ...

  9. go:defer

    defer:延迟. 假设有调用函数A.被调用函数B,其关系如下: func A(){//调用函数 ... defer B()//被调用函数 ... return//B将延迟到return前执行 } * ...

随机推荐

  1. Zookeeper命名服务——生成分布式有序且唯一id

    生成分布式有序且唯一id的方法有很多种,使用zookeeper是比较简单的一种方法,只是生成的速度不高,这里只是一个借助zk的版本号生成分布式唯一且有序id的例子. ZkIdGenerator.jav ...

  2. JAVA中Collection接口和Map接口的主要实现类

    Collection接口 Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements).一些Collection允许相同的元素 ...

  3. 【干货】Windows系统信息收集篇

    市场分析:计算机取证,就是应急响应.而应急响应的市场在于黑产的攻击频率.在当今的社会里,更多的人为了钱铤而走险的比比皆是,这个市场随着比特币,大数据,物联网的来临,规模将更加的庞大与有组织性.这将导致 ...

  4. Linux常见问题总结【转】

    作为一名合格的 Linux 运维工程师,一定要有一套清晰.明确的解决故障思路,当问题出现时,才能迅速定位.解决问题,这里给出一个处理问题的一般思路: 重视报错提示信息:每个错误的出现,都是给出错误提示 ...

  5. vim命令学习

    文本编辑器vim vim常用操作 vim是一个强大的全屏幕文本编辑器,是Linux上最常用的文本编辑器,它的作用是建立,编辑,显示文本文件. vim没有菜单,只有命令. 输入a或i或o进入编辑命令,下 ...

  6. MVC Form验证 登陆和退出Cookies的设定和消除

    红色部分为重点 1.webconfig配置  <system.web>节点下添加 <authentication mode="Forms"> <for ...

  7. 008_MAC 终端使用技巧

    一.常用终端命令. <1>reset 的作用很简单——将目前「终端」屏幕上的内容清空,就好像刚刚打开终端一样. <2>如果你在一条终端命令中发现有输入错误的话,那么用 cont ...

  8. MVC:分页改进URL

    http://localhost/?page=2     可以根据"可组合URL"创建一种更具吸引力的URL方案: http://localhost/page2     publi ...

  9. 利用openssl构建根证书-服务器证书-客户证书

    利用openssl构建根证书-服务器证书-客户证书 OpenSSL功能远胜于KeyTool,可用于根证书,服务器证书和客户证书的管理 一.构建根证书 1.构建根证书前,需要构建随机数文件(.rand) ...

  10. awk 调用 shell 命令,并传递参数

    from:awk 调用 shell 命令的两种方法:system 与 print shell 向awk传递命令,这样使用即可: awk -v  ...  但反过来呢?awk调用外部命令,同时也传参呢? ...