前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >go 中的 defer 使用及其规则

go 中的 defer 使用及其规则

作者头像
莫斯
发布2020-09-10 11:18:54
1.7K0
发布2020-09-10 11:18:54
举报
文章被收录于专栏:备份备份

1 defer 定义

defer 英文原意: vi. 推迟;延期;服从 vt. 使推迟;使延期。

defer的思想类似于C++中的析构函数,不过Go语言中“析构”的不是对象,而是函数,defer就是用来添加函数结束时执行的语句。

析构函数(destructor) 与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。 析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。

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

1.1 简单示例

  • 首先我们现用简单的例子来体验一下defer函数
代码语言:javascript
复制
package main

import (
    "fmt"
)

func main() {
    fmt.Println("Step 1")
    fmt.Println("Step 2")
    defer fmt.Println("Step Final")
    fmt.Println("Step 3")
    fmt.Println("Step 4")
}

输出

代码语言:javascript
复制
Step 1
Step 2
Step 3
Step 4
Step Final

我们将defer fmt.Println("Step Final") 换行, 其输出结果也如上所示

代码语言:javascript
复制
func main() {
	defer fmt.Println("Step Final")
    fmt.Println("Step 1")
    fmt.Println("Step 2")
    fmt.Println("Step 3")
    fmt.Println("Step 4")
}

输出

代码语言:javascript
复制
Step 1
Step 2
Step 3
Step 4
Step Final
  • 非主函数:
代码语言:javascript
复制
func f() (result int) {

  defer func() {
    result++
  }()
  return 0
}

上面 返回结果是 1,因为defer中添加了一个函数,在函数返回前改变了命名返回值的值。是不是很好用呢。但是,要注意的是,如果我们的defer语句没有执行,那么defer的函数就不会添加,如果把上面的程序改成这样:

代码语言:javascript
复制
func f() (result int) {

  return 0
  defer func() {
    result++
  }()
  return 0
}

上面的函数就返回0了,因为还没来得及添加defer的东西,函数就返回了。

1.2 具体示例

假设我们想要创建一个文件,写入它,然后在我们完成时关闭1。 在文件操作的时候,均需要进行关闭文件操作, 所以我们来用 defer 完成关闭文件操作。

代码语言:javascript
复制
package main

import "fmt"
import "os"

func main() {
    f := createFile("/tmp/defer.txt")
    defer closeFile(f)
    writeFile(f)
}

func createFile(p string) *os.File {
    fmt.Println("creating")
    f, err := os.Create(p)
    if err != nil {
        panic(err)
    }
    return f
}

输出:

代码语言:javascript
复制
creating
writing
closing

2 defer 常用场景

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

defer经常和 panic 以及 recover 一起使用,判断是否有异常,进行收尾操作。

代码语言:javascript
复制
package main
 
import "fmt"
 
func main(){
    defer func(){ // 必须要先声明defer,否则不能捕获到panic异常
        fmt.Println("c")
        if err:=recover();err!=nil{
            fmt.Println(err) // 这里的err其实就是panic传入的内容,55
        }
        fmt.Println("d")
    }()
    f()
}
 
func f(){
    fmt.Println("a")
    panic(55)
    fmt.Println("b")
    fmt.Println("f")
}

输出结果:

代码语言:javascript
复制
a
c
55
d

3 defer 规则

3.1 当申明defer 时,参数就已经解析了

代码语言:javascript
复制
func a() {
	i := 0
	defer fmt.Println(i)
	i++
	return
}

输出

代码语言:javascript
复制
0

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

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

这是因为虽然我们在defer后面定义的是一个带变量的函数: fmt.Println(i). 但这个变量(i)在defer被声明的时候,就已经确定其确定的值了2

3.2 defer执行顺序为先进后出

当同时定义了多个defer代码块时,golang安装先定义后执行的顺序依次调用defer。

代码语言:javascript
复制
func b() {
	for i := 0; i < 4; i++ {
		defer fmt.Print(i)
	}
}

输出:

代码语言:javascript
复制
3
2
1
0

3.3 defer可以读取有名返回值

代码语言:javascript
复制
func c() (i int) {
	defer func() { i++ }()
	return 1
}

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

  1. https://gobyexample.com/defer
  2. https://studygolang.com/articles/10167
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/05/27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 defer 定义
    • 1.1 简单示例
      • 1.2 具体示例
      • 2 defer 常用场景
      • 3 defer 规则
        • 3.1 当申明defer 时,参数就已经解析了
          • 3.2 defer执行顺序为先进后出
            • 3.3 defer可以读取有名返回值
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档