在golang当中,defer代码块会在函数调用链表中增加一个函数调用。这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用。因此,defer通常用来释放函数内部变量。
通过defer,我们可以在代码中优雅的关闭/清理代码中所使用的变量。defer作为golang清理变量的特性,有其独有且明确的行为。以下是defer三条使用规则。

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

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

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

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

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

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

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

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

再看一个例子:

package mainimport "fmt"
func f1() (result int) {defer func() {result++}()return 0
}func f2() (r int) {t := 5defer func() {t = t+5}()return t
}func f3() (t int) {t = 5defer func() {t = t+5}()return t
}
func f4() (r int) {defer func(r int) {r = r + 5}(r)return 1
}func main() {fmt.Println(f1())fmt.Println(f2())fmt.Println(f3())fmt.Println(f4())
}

运行结果是:

1
5
10
1

函数返回的过程是这样子的:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。

defer表达式可能会在设置函数返回值之后,在返回到调用函数之前,修改返回值,使最终的函数返回值与你想象的不一致。

可以将return xxx改成
返回值=xxx
调用defer函数
空的return

那上面的例子就可以改成:

func f11() (result int) {result = 0   //先给返回值赋值func(){               //再执行defer 函数result++}()return                //最后返回
}func f22() (r int) {t := 5r = t //赋值指令func() {  //defer 函数被插入到赋值与返回之间执行,这个例子中返回值r没有被修改t = t+5}return   //返回
}func f33() (t int) {t = 5    //赋值指令func(){t = t+5  //然后执行defer函数,t值被修改}return
}
func f44() (r int) {r = 1    //给返回值赋值func(r int){   //这里的r传值进去的,是原来r的copy,不会改变要返回的那个r值r = r+5}(r)return
}

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

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

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

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

再看之前的那个例子:

package mainimport "fmt"func main() {fmt.Println("a return:", a()) // 打印结果为 a return: 0
}func a() int {var i intdefer func() {i++fmt.Println("a defer2:", i) // 打印结果为 a defer2: 2}()defer func() {i++fmt.Println("a defer1:", i) // 打印结果为 a defer1: 1}()return i
}

结果是:

a defer1: 1
a defer2: 2
a return: 0

规则三 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.

看这个例子:

package mainimport "fmt"
func trace(s string) string {fmt.Println("entering:",s)return s
}func un(s string) {fmt.Println("leaving:",s)
}func a() {defer un(trace("a"))fmt.Println("in a")
}func b() {defer un(trace("b"))fmt.Println("in b")a()
}func main() {b()
}

运行结果是:

entering: b
in b
entering: a
in a
leaving: a
leaving: b

golang defer的使用相关推荐

  1. golang defer实现

    golang defer实现 1.现象 defer 会在函数return前执行 如果发生非系统级别panic,defer依然执行:在defer执行过程中,如果有recover,那就捕获panic,否则 ...

  2. golang defer简介 goland 警告提示 possible resource leak,difer is called in a for loop 原因

    目录 警告原因 解决方法 defer理解 defer调用是一个栈结构 defer的作用域是一个函数,不是一个语句块 链式调用 针对非指针类型调用函数 警告原因 在for中使用defer关闭资源,其实资 ...

  3. Golang defer 快速上手

    文章目录 1.简介 2.注意事项 2.1 defer 函数入参在 defer 时确定 2.2 defer 执行顺序为后进先出 2.3 defer 函数在 return 语句赋值与返回之间执行 2.4 ...

  4. Golang defer

    在Golang使用defer常常会迷惑于以下两个问题 defer 关键字的调用时机以及多次调用 defer 时执行顺序是如何确定的: defer 关键字使用传值的方式传递参数时会进行预计算,导致不符合 ...

  5. Golang defer解读

    defer defer是Go语言提供的一种用于注册延迟调用的机制:让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行. defer语句通常用于一些成对 ...

  6. golang defer 关闭文件 报错file may have nil or other unexpected value as its corresponding error

    错误实例: file, err := os.Open("xxx.txt") defer file.Close() if err != nil {return err } 初学者很多 ...

  7. golang defer使用——资源关闭时候多用

    defer Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句.当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回.特别是当你在进行一些打开资源 ...

  8. golang——defer

    defer是什么 defer是Go语言提供的一种用于注册延迟调用的机制,让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行. defer与panic和 ...

  9. [Golang]defer详解

    数据结构 defer的数据结构定义在$GOROOT/src/runtime/runtime2.go // 大体定义如下,忽略少部分字段 type _defer struct {sp uintptr / ...

最新文章

  1. SAP S/4HANA BP功能
  2. Windows Server 2008 R2 如何显示被隐藏的文件扩展名
  3. HTML5实例教程:OL标签的start属性和reversed属性
  4. 数据结构源码笔记(C语言):直接选择排序
  5. Spring事务的实现方式和实现原理
  6. java使用:: 表达式_Java 12:开关表达式
  7. 拆分-洛谷P2745 [USACO5.3]窗体面积Window Area
  8. php清理html table样式,Parse HTML Table - PHP [closed]
  9. 【Win10】【Win2D】实现控件阴影效果
  10. 上海飞国内最远是哪里_讯飞连发三款智能录音笔!可离线转写拍视频秒配字幕,首推智能TWS耳机...
  11. 使用R包GD实现地理探测器算法
  12. VMware安装Windows XP虚拟机并手动安装外加驱动程序
  13. 在龙芯3A3000中标麒麟7.0环境下编译golang1.14.1源码
  14. 野火指南者(STM32F103)移植LVGL
  15. 软件工程实训项目(一)——IDEA连接Mysql数据库
  16. MongoDB和MySQL常用增删改查语句
  17. 优雅地寻找网站源码(一)
  18. 复制粘贴Excel文件后,显示“安全警告 宏已被禁用”,复制后得到的文件看不到内容
  19. Java斗_Java集合练习:斗地主游戏
  20. 【洛谷P5514】永夜的报应【模拟】

热门文章

  1. 4G EPS 中建立 UE 与 eNB 之间的 RRC 连接
  2. 人人都是 DBA(VII)B 树和 B+ 树
  3. 【转】strlen源码
  4. php 一次性替换多个关键词
  5. Ceph Storage Cluster(CEPH存储集群) Configuration配置
  6. Makefile 实现工程的本地部署
  7. cropped-img_2692.jpg
  8. xi mapping function-concat
  9. centos 创建 logrotate 进行日志分割
  10. laravel的工厂模式数据填充: