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. 第5月第13天 node cnpm安装 babel

    1. https://nodejs.org/en/download/ http://www.runoob.com/react/react-install.html 2. npm install --s ...

  2. [转]避免头文件重复包含以及#ifndef 与 #program once 的区别

    为了避免同一个文件被include多次,C/C++中有两种方式,一种是#ifndef方式,一种是#pragma once方式.在能够支持这两种方式的编译器上,二者并没有太大的区别,但是两者仍然还是有一 ...

  3. hibernate的多对多关联映射

    在我们实际项目中,多对多的情况也时长存在,比如最常见的就是系统管理的五张表,如下面的一个结构: 在本文学习hibernate多对多关联映射的实验中我简单的写几个字段,达到学习目的即可. 1.多对多的关 ...

  4. C++笔试易错题集(持续更新)

    1.如下代码输出结果是什么? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include<stdio.h> char *myString() {     ...

  5. umount /mnt/cdrom

    这是因为有程序正在访问这个设备,最简单的办法就是让访问该设备的程序退出以后再umount.可能有时候用户搞不清除究竟是什么程序在访问设备,如果用户不急着umount,则可以用: umount -l / ...

  6. mysql-8.0.11-winx64 免安装版配置方法

    mysql-8.0.11-winx64.zip  下载地址:https://dev.mysql.com/downloads/file/?id=476233 mysql-8.0.11-winx64.zi ...

  7. 004_加速国内docker源下载速度

    docker下载慢的不行.国内加速器地址 http://355dbe53.m.daocloud.iohttps://docker.mirrors.ustc.edu.cn https://hub-mir ...

  8. 公司软raid问题

    RAID的技术介绍: stripe width(条带宽度):RAID中的磁盘数,就是组成这个stripe的磁盘数.如,4个磁盘组成的RAID 0,条带宽度就是4. stripe depth(条带深度) ...

  9. asp.net后台获取前台页面大小

    前台代码如下:<input type="hidden" runat="server" value="0" id="txBod ...

  10. supervisor安装部署和使用实例

    Supervisord是用Python实现的一款非常实用的进程管理工具,类似于monit,monit和supervisord的一个比较大的差异是supervisord管理的进程必须由superviso ...