defer关键字

defer关键字的作用是当外围函数返回之后才执行被推迟的函数。在文件输入输出操作中经常可以见到defer关键字,因为它使您不必记住何时关闭已打开的文件:defer关键字调用文件关闭函数关闭已打开的文件时,可以紧靠着文件打开函数之后。在第八章“告诉Unix系统要做什么”中,会介绍如何在文件相关操作中使用defer关键字,本节将介绍defer的其他用法。您还将在panic()recover()两个Go内置函数中看到defer操作。

首先记住一点重要的原则:defer函数在外围函数返回之后,以后进先出(LIFO)的原则执行。简单点说,在一个外围函数中有3个defer函数:f1()最先出现,然后f2(),最后f3(),当外围函数执行返回之后,f3()最先被执行,接着是f2(),最后是f1()

如果你感觉这个定义不是很清晰,查看并执行defer.go源码,这应该会让你理解的更清楚些。下面分三部分呈现这段源码。

源码第一部分:

package main
import (
    "fmt"
)
func d1() {
    for i := 3; i > 0; i-- {
        defer fmt.Print(i, " ")
    }
}

上面的Go代码实现了一个名为d1()函数,函数里面有一个for循环和一个defer语句,这个defer语句将会执行三次。

第二部分源码:

func d2() {
    for i := 3; i > 0; i-- {
        defer func() {
            fmt.Print(i, " ")
        }()
    }
    fmt.Println()
}

这部分代码实现了d2()函数,它也包含了一个for循环和一个defer语句,这个defer语句也会执行三次,但是这个defer执行的是匿名函数,并且匿名函数没有带参数。

最后一部分源码:

func d3() {
    for i := 3; i > 0; i-- {
        defer func(n int) {
            fmt.Print(n, " ")
        }(i)
    }
}
func main() {
    d1()
    fmt.Println()
    d2()
    fmt.Println()
    d3()
    fmt.Println()
}

这部分代码,定义了d3()函数,它包含了for循环defer语句,这个defer执行带参数的匿名函数,其中参数n使用的是循环中变量i的值。main()函数调用这三个函数。

执行源码会得到如下输出:

$ go run defer.go
1 2 3
0 0 0
1 2 3

您很可能会发现生成的输出很复杂且难以理解。 这表明,如果您的代码不清晰或不明确,执行defer操作可能会产生非常棘手的结果。

让我们分析一下上面的结果,更进一步了解如果不密切关注自己的代码,defer将会变得多么棘手。第一行是d1()函数输出的(1 2 3),变量i的值在这个函数中顺序是3 、2和1 ,在d1()中延迟的函数是fmt.Print()。 因此,当d1()函数即将返回时,您将以相反的顺序获取for循环中变量i的三个值,因为被延迟的函数以LIFO顺序执行。

第二行是d2()函数的输出,很奇怪的是我们得到了单个0,而不是想象中的1、2和3 ,原因很简单,在循环结束后,i的值为0,因为是0值使循环终止。但是,这里棘手的部分是在循环结束后会执行被延迟的匿名函数。因为它没有参数,这意味着,将值为0的i进行三次打印输出! 在您的项目中,这种令人困惑的代码可能导致令人讨厌的错误,所以尽量避免它!

第三行是d3()函数的输出,因为匿名函数的参数的存在,每次匿名函数被推迟执行的时候,它都会获取并使用变量i当前的值,所以每次执行匿名函数都有不同的值输出。

到这里,你应该清楚了,使用defer的最好的方式就是第三个函数展示的方法,这是因为您故意以易于理解的方式在匿名函数中传递所需的变量。

最后编辑: kuteng  文档更新时间: 2021-03-27 20:14   作者:kuteng