Defer

什么是defer

defer翻译成中文是推迟的意思。defer语句用来推迟一个函数的执行,具体来说就是推迟到defer语句所在函数的最后。听起来有点晦涩难懂,我们通过一个例子来理解下。

package main

import "fmt"

func finished() {
    fmt.Println("Finished finding largest")
}

func largest(nums []int) {
    defer finished()
    fmt.Println("Started finding largest")
    max := nums[0]
    for _, v := range nums {
        if v > max {
            max = v
        }
    }
    fmt.Println("Largest number in", nums, "is", max)
}

func main() {
    nums := []int{78, 109, 2, 563, 300}
    largest(nums)
}

这段程序比较简单,找到一个切片的最大值并打印。注意函数largest的第一行语句defer finished(),会推迟函数finished()的执行。defer所在的函数为largest,因此推迟到largest函数体的最后,即return之前。该程序输出如下:

defer方法

前面的例子我们推迟了一个函数的执行。不过也可以推迟方法的执行。

这里我们推迟了一个方法的执行defer p.fullName()。该程序比较简单,输出如下:

defer函数/方法的参数

defer执行的函数/方法的入参在defer语句执行时就已确定并计算好,而不是在函数/方法调用时。我们通过一个例子来理解下。

这里a的初始值为5,然后执行defer语句,将a值传递给被defer的函数printA,这时入参就已经确定了,即便后面我们又把a的值更新为10printA执行时的入参仍然是5。该程序输出如下:

defer栈

如果一个函数内同时出现了多个defer,那么这些defer是按照栈结构来执行的,即先defer的后执行,后defer的先执行的顺序。下面我们通过这个特性来将一个字符串反转下:

这里的for range循环遍历字符串的每个字符,执行defer fmt.Printf("%c", v)语句,由于被defer的函数是栈顺序,因此打印的字符串是反转过来的。该程序输出结果为:

defer真实适用场景

下面我们看下defer真正适用的场景。我们先不使用defer来实现下面的程序。

这里声明了结构体类型rect,即长方形类型,并在该结构体类型上定义了方法area用来计算面积。该方法内需要判断入参的合法性,即是否都是非负数。合法性判断完成后才打印输出长方形的面积。area方法内部有三个分支,r.length < 0r.width < 0以及正常情况。但是每个分支在执行完对应逻辑时都需要调用wg.Done()来通知协程执行完毕,接着调用return退出方法的执行。这种场景非常适合用defer,因为我们无论执行哪个分支都需要在return前调用wg.Done(),因此可以将所有分支中的wg.Done()抽出来放到defer语句中。这样代码可读性更高,而且容易维护。 下面我们用defer重构下:

该程序输出如下:

这里使用defer还有另外一个好处,假设我们后面又新增了一个判断分支,如果不是用defer,那么我们还得留心加上语句wg.Done()。因此这种场景还是使用defer更好,代码的可读性、可扩展性都比较好。

Last updated

Was this helpful?