先抛砖引玉defer的延迟调用:
defer特性:

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

defer用途:

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

好,废话不多说,实例加深理解,我们先看看一段代码

package main

import "fmt"

func main() {
    var users [5]struct{}
    for i := range users {
        defer fmt.Println(i)
    }
}

输出:4 3 2 1 0 ,defer 是先进后出,这个输出没啥好说的。

我们把上面的代码改下:
defer 换上闭包

package main

import "fmt"

func main() {
    var users [5]struct{}
    for i := range users {
        defer func() { fmt.Println(i) }()
    }
}

输出:4 4 4 4 4,很多人也包括我。预期的结果不是 4 3 2 1 0 吗?官网对defer 闭包的使用大致是这个意思:

函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成4,所以输出全都是4。那么 如何正常输出预期的 4 3 2 1 0 呢?
不用闭包,换成函数:

package main

import "fmt"

func main() {
    var users [5]struct{}
    for i := range users {
        defer Print(i)
    }
}
func Print(i int) {
    fmt.Println(i)
}

函数正常延迟输出:4 3 2 1 0。

我们再举一个可能一不小心会犯错的例子:
defer调用引用结构体函数

package main

import "fmt"

type Users struct {
    name string
}

func (t *Users) GetName() { // 注意这里是 * 传地址 引用Users
    fmt.Println(t.name)
}
func main() {
    list := []Users{{"乔峰"}, {"慕容复"}, {"清风扬"}}
    for _, t := range list {
        defer t.GetName()
    }
}

输出:清风扬 清风扬 清风扬。

这个输出并不会像我们预计的输出:清风扬 慕容复 乔峰

可是按照前面的go defer函数中的使用说明,应该输出清风扬 慕容复 乔峰才对啊?

那我们换一种方式来调用一下

package main

import "fmt"

type Users struct {
    name string
}

func (t *Users) GetName() { // 注意这里是 * 传地址 引用Users
    fmt.Println(t.name)
}
func GetName(t Users) { // 定义一个函数,名称自定义
    t.GetName() // 调用结构体USers的方法GetName
}
func main() {
    list := []Users{{"乔峰"}, {"慕容复"}, {"清风扬"}}
    for _, t := range list {
        defer GetName(t)
    }
}

输出:清风扬 慕容复 乔峰。

这个时候输出的就是所谓"预期"滴了

当然,如果你不想多写一个函数,也很简单,可以像下面这样(改2处),同样会输出清风扬 慕容复 乔峰

package main

import "fmt"

type Users struct {
    name string
}

func (t *Users) GetName() { // 注意这里是 * 传地址 引用Users
    fmt.Println(t.name)
}
func GetName(t Users) { // 定义一个函数,名称自定义
    t.GetName() // 调用结构体USers的方法GetName
}
func main() {
    list := []Users{{"乔峰"}, {"慕容复"}, {"清风扬"}}
    for _, t := range list {
        t2 := t // 定义新变量t2 t赋值给t2
        defer t2.GetName()
    }
}

输出:清风扬 慕容复 乔峰。

通过以上例子,结合

我们可以得出下面的结论:

defer后面的语句在执行的时候,函数调用的参数会被保存起来,但是不执行。也就是复制了一份。但是并没有说struct这里的*指针如何处理,

通过这个例子可以看出go语言并没有把这个明确写出来的this指针(比如这里的* Users)当作参数来看待。到这里有滴朋友会说。看似多此一举的声明,

直接去掉指针调用 t *Users改成 t Users 不就行了?

package main

import "fmt"

type Users struct {
    name string
}

func (t Users) GetName() { // 注意这里是 * 传地址 引用Users
    fmt.Println(t.name)
}

func main() {
    list := []Users{{"乔峰"}, {"慕容复"}, {"清风扬"}}
    for _, t := range list {
        defer t.GetName()
    }
}

输出:清风扬 慕容复 乔峰。这就回归到上面的 defer 函数非引用调用的示例了。所以这里我们要注意defer后面的指针函数和普通函数的调用区别。很容易混淆出错。

多个 defer 注册,按 FILO 次序执行 ( 先进后出 )。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行,我们看看这一段

package main

func users(i int) {
    defer println("北丐")
    defer println("南帝")

    defer func() {
        println("西毒")
        println(10 / i) // 异常未被捕获,逐步往外传递,最终终止进程。
    }()

    defer println("东邪")
}

func main() {
    users(0)
    println("武林排行榜,这里不会被输出哦")
}

输出:

东邪
西毒
南帝
北丐
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.users.func1(0x0)

我们发现函数中异常,最后才捕获输出,但是一旦捕获了异常,后面就不会再执行了,即终止了程序。

*延迟调用参数在求值或复制,指针或闭包会 "延迟" 读取。

package main

func test() {
    x, y := "乔峰", "慕容复"

    defer func(s string) {
        println("defer:", s, y) // y 闭包引用 输出延迟和的值,即y+= 后的值=慕容复第二
    }(x) // 匿名函数调用,传送参数x 被复制,注意这里的x 是 乔峰,而不是下面的 x+= 后的值

    x += "第一"
    y += "第二"
    println("x =", x, "y =", y)
}

func main() {
    test()
}

输出:

x = 乔峰第一 
y = 慕容复第二
defer: 乔峰 慕容复第二

defer 与 return注意

package main

import "fmt"

func Users() (s string) {

    s = "乔峰"
    defer func() {
        fmt.Println("延迟执行后:"+s)
    }()

    return "清风扬"
}

func main() {
    Users() // 输出:延迟执行后:清风扬
}

解释:在有命名返回值的函数中(这里命名返回值为 s),执行 return "风清扬" 的时候实际上已经将s 的值重新赋值为 风清扬。

所以defer 匿名函数 输出结果为 风清扬 而不是 乔峰。

在错误的位置使用 defer,来一段不严谨滴代码:

package main

import "net/http"

func request() error {
    res, err := http.Get("http://www.google.com") // 不翻墙的情况下。是无法访问滴
    defer res.Body.Close()
    if err != nil {
        return err
    }

    // ..继续业务code...

    return nil
}

func main() {
    request()
}

输出:

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x40 pc=0x5e553e]

Why?因为在这里我们并没有检查我们的请求是否成功执行,当它失败的时候,我们访问了 Body 中的空变量 res ,所以会抛出异常。

解决方案

总是在一次成功的资源分配下面使用 defer ,对于这种情况来说意味着:当且仅当 http.Get 成功执行时才使用 defer...待续。。。

内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/phpper/p/11984161.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!