爱上开源之golang入门至实战第四章函数(Func)(九)

爱上开源之golang入门至实战第四章函数(Func)(九)

4.4.9 defer 函数

前言

在前面的介绍函数的文章时候,就在java和go对异常处理的地方提到过这个defer函数,defer是go语言里进行延迟调用的一种机制,defer后面的函数不会马上就执行调用,只有在当前调用defer函数的函数完全执行完毕后才会执行defer函数,基于这样的调用机制及原理,defer函数通常都用来进行资源的释放。例如

func TestDeferFun(t *testing.T) {
    defer fmt.Println("Defer Func")
​
    fmt.Println("Test")
}
​
===== OUTPUT =====
Test
Defer Func

如上述代码,通过defer执行的函数fmt.Println并不会马上执行调用,而是在执行defer函数的函数执行完毕后再进行执行;所以先执行了fmt.Println("Test"), 然后再执行fmt.Println("Defer Func");

 

今天这篇文章就来讲一讲Go函数里的Defer函数。

Defer函数是延迟执行,即在调用Defer函数的函数执行完毕以后,才进行执行,如果有多个defer函数都被调用执行的话,这些被调用执行的defer和调用顺序又有什么关系呢?

多个defer函数调用的顺序

下面来我们把上面的代码改进一下;看看下面的代码样例

func TestDeferFun2(t *testing.T) {
    defer fmt.Println("Defer Func 1")
    defer fmt.Println("Defer Func 2")
    defer fmt.Println("Defer Func 3")
​
    fmt.Println("Test")
}
​
​
===== OUTPUT =====
Test
Defer Func 3
Defer Func 2
Defer Func 1

这个代码依次调用了三次defer函数, 分别依次打印"Defer Func 1", "Defer Func 2", "Defer Func 3" 延迟函数在函数执行完成才进行执行,所以上面打印的这三句代码,都在fmt.Println("Test")执行完之后, 而且依次执行的三行代码,是先进后出的调用方式,先执行调用的defer函数,在函数执行完后,执行defer函数的过程中是最后执行的。记住这个执行顺序。

return对defer的影响

下面我们再来看看代码

func TestDeferFun3(t *testing.T) {
​
    defer fmt.Println("Defer Func 1")
    defer fmt.Println("Defer Func 2")
    defer fmt.Println("Defer Func 3")
​
    fmt.Println("Test")
​
    if 1 < 2 {
        return
    }
​
    defer fmt.Println("Defer Func 4")
}
​
​
===== OUTPUT =====
Test
Defer Func 3
Defer Func 2
Defer Func 1

通过上面的代码可以看到,return作为返回值,对defer的执行次序没有影响,return结束也表示函数已经执行完成,还是会继续执行defer函数,而且多个defer依次调用的情况下,依然还是按照先进后出的顺序进行执行。

Panic对defer的影响

下面我们再来看看代码

func TestDeferFun4(t *testing.T) {
​
    defer fmt.Println("Defer Func 1")
    defer fmt.Println("Defer Func 2")
    defer fmt.Println("Defer Func 3")
​
    fmt.Println("Test")
​
    panic("This is a testing")
}
​
​
===== OUTPUT =====
Test
Defer Func 3
Defer Func 2
Defer Func 1
--- FAIL: TestDeferFun4 (0.00s)
panic: This is a testing [recovered]
    panic: This is a testing
​
goroutine 6 [running]:
testing.tRunner.func1.2({0x8ae3e0, 0x98b4f0})
    E:/WORK/SOFT/go1.18.windows-amd64/go/src/testing/testing.go:1389 +0x477

通过上面的代码可以看到,当执行调用defer函数的函数出现panic时, 函数执行完毕,依然会执行效用defer函数, 而且依次执行调用defer函数的顺序仍然依照先进后出的顺序进行执行。

通过defer处理程序panic

在上面的代码中,我们调用过程中,在函数里进行了panic的调用,导致函数执行出现系统异常,在执行完defer以后,程序就会退出整个函数程序,虽然执行了defer函数,但是程序已经退出了主线程,而无法继续下去, 在java中可以使用try catch的方式,来对异常进行处理,而继续程序不退出主程序, 在go语言里亦然也有方法来处理类似的场景, 来看看下面的代码

func TestDeferFun5(t *testing.T) {
    defer func() {
        if error := recover(); error != nil {
            defer fmt.Printf("Defer Func 1 error: %v\n", error)
        } else {
            defer fmt.Println("Defer Func 1 not error")
        }
    }()
    defer fmt.Println("Defer Func 2")
    defer fmt.Println("Defer Func 3")
​
    fmt.Println("Test")
​
    panic("This is a testing")
}
​
===== OUTPUT =====
Test
Defer Func 3
Defer Func 2
Defer Func 1 error: This is a testing

可以使用recover()函数 从 panic里获取错误,然后进行处理, 如同java里的catch函数, 通过defer 和recover的搭配的方式就可以实现类似java的catch效果。

上面代码的例子里,处理recover的函数,使用的匿名函数的方式,我们可以把匿名函数封装到一个定义的函数里也有同样的效果,可以把上面的代码改成如下的方式;

func processError() {
    if error := recover(); error != nil {
        defer fmt.Printf("Defer Func 1 error: %v\n", error)
    } else {
        defer fmt.Println("Defer Func 1 not error")
    }
}
​
func TestDeferFun6(t *testing.T) {
    defer processError()
    defer fmt.Println("Defer Func 2")
    defer fmt.Println("Defer Func 3")
​
    fmt.Println("Test")
​
    panic("This is a testing")
}
​
​
===== OUTPUT =====
Test
Defer Func 3
Defer Func 2
Defer Func 1 error: This is a testing

上面的processError是一个处理panic的函数, 通过defer的方式执行和调用processError,在代码下面执行到panic以后,函数执行结束,依次执行Defer Func3, Defer Func2, 再执行processError, 通过recover获取到error,然后打印Error,也就得到了执行结果Defer Func 1 error: This is a testing;

上面的代码仅仅演示功能的效果, 把执行错误的代码直接写死到了,封装的函数里,不是个好的方法,我们还可以通过传入函数来进行进一步的封装,如下代码

type DoError func(err error)
​
func processErrorPlus(doError DoError) {
    if e := recover(); e != nil {
        if doError != nil {
            var err error
            switch x := e.(type) {
            case string:
                err = errors.New(x)
            case error:
                err = x
            default:
                err = errors.New("unknown panic")
            }
​
            doError(err)
        }
    } else {
        defer fmt.Println("Defer Func 1 not error")
    }
}
​
func TestDeferFun7(t *testing.T) {
    defer processErrorPlus(func(err error) {
        defer fmt.Printf("Defer Func 1 error: %v\n", err)
    })
    defer fmt.Println("Defer Func 2")
    defer fmt.Println("Defer Func 3")
​
    fmt.Println("Test")
​
    panic("This is a testing")
}
​
​
===== OUTPUT =====
Test
Defer Func 3
Defer Func 2
Defer Func 1 error: This is a testing
--- PASS: TestDeferFun7 (0.00s)

结束语

今天这个文章只是简单的介绍了defer的用法,在后面的几个文章,我们还会通过更多的代码样例来深入的看看defer的使用技巧和场景。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

inthirties

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值