golang中defer的使用规则详解

作者:andy zhang 时间:2023-07-21 22:47:31 

前言

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

为了更好的学习defer的行为,我们首先来看下面一段代码:


func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}

dst, err := os.Create(dstName)
if err != nil {
return
}

written, err = io.Copy(dst, src)
dst.Close()
src.Close()
return
}

这段代码可以运行,但存在'安全隐患'。如果调用dst, err := os.Create(dstName)失败,则函数会执行return退出运行。但之前创建的src(文件句柄)没有被释放。 上面这段代码很简单,所以我们可以一眼看出存在文件未被释放的问题。 如果我们的逻辑复杂或者代码调用过多时,这样的错误未必会被及时发现。 而使用defer则可以避免这种情况的发生,下面是使用defer的代码:


func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()

dst, err := os.Create(dstName)
if err != nil {
return
}
defer dst.Close()

return io.Copy(dst, src)
}

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

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

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


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

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

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

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


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

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


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

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

但为什么是先输出1,在输出0呢? 看下面的规则二。

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

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


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

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

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

掌握了defer以上三条使用规则,那么当我们遇到defer代码块时,就可以明确得知defer的预期结果。

来源:https://chinazt.cc/2017/06/30/deferde-shi-yong-gui-ze/

标签:golang,defer,使用规则
0
投稿

猜你喜欢

  • SQL Server如何保证可空字段中非空值唯一

    2011-02-24 16:44:00
  • Python中OpenCV图像特征和harris角点检测

    2023-08-04 22:32:56
  • Python利用matplotlib.pyplot绘图时如何设置坐标轴刻度

    2023-09-18 23:32:01
  • mysql利用覆盖索引避免回表优化查询

    2024-01-12 21:34:11
  • 使用vue与jquery实时监听用户输入状态的操作代码

    2023-07-02 17:08:02
  • Django Python 获取请求头信息Content-Range的方法

    2022-09-09 07:04:53
  • 对跨多个表格的数据组合时需要用到的SQL

    2009-01-06 11:18:00
  • 分享20个数据库设计的最佳实践

    2024-01-24 09:28:53
  • 谈ASP的未来

    2009-03-24 20:35:00
  • javascript验证form表单数据的案例详解

    2024-04-10 10:39:05
  • Python中的闭包总结

    2023-09-09 03:46:05
  • C#操作SQLite数据库之读写数据库的方法

    2024-01-27 21:46:33
  • PyQt5中QTimer定时器的实例代码

    2021-06-01 07:28:54
  • Python continue语句用法实例

    2021-02-16 07:40:00
  • mysql jdbc连接步骤及常见参数

    2024-01-12 15:49:45
  • Python读取一个目录下所有目录和文件的方法

    2023-05-30 23:04:21
  • Golang自定义结构体转map的操作

    2024-05-08 10:21:39
  • Webots下载安装 + Pycharm联调使用教程

    2023-02-20 23:35:43
  • python+pillow绘制矩阵盖尔圆简单实例

    2022-07-04 02:44:39
  • Python 列表排序方法reverse、sort、sorted详解

    2021-10-18 10:11:52
  • asp之家 网络编程 m.aspxhome.com