defer特性:

  1. 关键字 defer 用于注册延迟调用。
  2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。
  3. 多个defer语句,按先进后出的方式执行。
  4. defer语句中的变量,在defer声明时就决定了。

defer用途:

  1. 关闭文件句柄
  2. 锁资源释放
  3. 数据库连接释放

根据开发文档描述,下面来做一一验证。

1.defer用于注册延迟调用。

package main

import "fmt"

func main()  {
    fmt.Println("测试defer start")
    defer func() {
        fmt.Println("defer func")
    }()
    fmt.Println("测试defer end")
}

执行结果为:
image.png

证实defer函数用来延迟调用,在函数结束的时候被调用。函数结束包括:被调用方法正常return,或者到达方法体结尾,也或者包含defer函数的方法在执行panic的时候都会执行defer函数。

值得注意是,当包含defer函数的方法在执行panic操作的时候,会先执行defer函数,再执行panic方法,panic之后的语句是不可达的。下面我用实例来再次证明一下。

package main

import "fmt"

func main()  {
    fmt.Println("测试defer start")
    defer func() {
        fmt.Println("defer func")
    }()
    fmt.Println("测试defer end")
    panic("panic 异常")
}

执行结果为:
image.png

由此看出,defer发生在panic执行之前,因此,可以在defer方法里面做资源清理,释放操作。

2.多个defer语句,按先进后出的方式执行。

package main

import "fmt"

func main()  {
    deferFunc1()
}

func deferFunc1()  {

    val := "1111"
    defer fmt.Println("val1",val)
    defer func() {
        fmt.Println("val2",val)
    }()
    defer fmt.Println("val3",val)

    defer func(val string) {
        fmt.Println("val4",val)
    }(val)
    defer fmt.Println("val5",val)

    fmt.Println("val",val)
}

执行结果为:
image.png

简单理解就是:定义defer类似于入栈操作,执行defer类似于出栈操作,先进后出。

3.defer语句中的变量,在defer声明时就决定了。

package main

import "fmt"

func main()  {
    deferFunc1()
}

func deferFunc1()  {

    val := "1111"
    defer fmt.Println("val1",val)
    defer func() {
        fmt.Println("val2",val)
    }()
    defer fmt.Println("val3",val)

    val = "2222"
    defer func(val string) {
        fmt.Println("val4",val)
    }(val)
    defer fmt.Println("val5",val)

    val = "3333"
    defer func() {
        fmt.Println("val6",val)
    }()
    fmt.Println("val",val)
}

此时,可以考虑一下上面程序输出结果是啥?为什么?针对上面程序有几个易错点,经常被作为面试的笔试题来考察求职者对defer的理解。

下面看一张很经典的图片,golang程序调用顺序图。

image.png

var 这些变量是按照由上到下赋值的,结合上面程序来说,val变量最后被赋值为"3333",而defer又是推迟函数,按理说所有defer包含的延迟函数的val都是"3333"才对,但是执行结果出乎我们的意料。

image.png

由上面结果可以看出:
defer直接执行fmt.Println函数,相当于func(val string) val为形参,最后结果由实参决定。
defer执行func(){}()时,val变量的值对应最后赋值的值。这里还有一种特殊情况,defer func(val string) {}(val) 将变量传递到defer函数里面,此时func(val string) val为形参,它的值受传递的实参来决定的。