0
点赞
收藏
分享

微信扫一扫

28.Go异常处理-延迟调用defer

28.Go异常处理-延迟调用defer

3 延迟调用defer

3.1  defer基本使用

函数定义完成后,只有调用函数才能够执行,并且一经调用立即执行。例如:

fmt.Println("hello world")
fmt.Println("I am regal")

先输出“hello world”,然后再输出“I am regal”

但是关键字 defer ⽤于延迟一个函数(或者当前所创建的匿名函数)的执行。注意,defer语句只能出现在函数的内部。

基本用法如下:

defer fmt.Println("hello world") // 延迟调用
fmt.Println("I am regal")
fmt.Println("print 3.....")

执行如下:

I am regal
print 3.....
hello world # 最后延迟调用

defer的应用场景:

defer的应用场景:文件操作,先打开文件,执行读写操作,最后关闭文件。为了保证文件的关闭能够正确执行,可以使用defer.

大家可以先看一下文件操作的伪代码,来体会一下关于defer的 场景,关于文件操作,我们后面会详细的讲解。

import (
"fmt"
"io"
"os"
)

func CopyFile(dstName, srcName string) (written int64, err error) {
//根据传递过来的参数(文件名)打开文件
src, err := os.Open(srcName)

// 如果打开文件时出现错误,退出整个函数
if err != nil {
return
}

// 创建文件
dst, err := os.Create(dstName)
if err != nil {
return
}

written, err = io.Copy(dst, src)

// 关闭打开的文件
dst.Close()
src.Close()

return
}

以上代码就是,打开文件,创建文件,执行文件拷贝的操作,最后将文件进行关闭。

但是问题时,如果假设在执行文件打开时,出现了问题,那么就会执行如下代码:

if err != nil {
return
}

退出整个函数,那么就不会执行文件的关闭操作。

所以为了解决这个问题,现在将程序进行修改,如下所示:

func CopyFile(dstName, srcName string) (written int64, err error) {
//根据传递过来的参数(文件名)打开文件
src, err := os.Open(srcName)

// 如果打开文件时出现错误,退出整个函数
if err != nil {
return
}

// 修改:使用defer保存CopyFile函数退出时,执行文件关闭
defer src.Close()

// 创建文件
dst, err := os.Create(dstName)
if err != nil {
return
}

// 修改:使用defer保存CopyFile函数退出时,执行文件关闭
defer dst.Close()

return io.Copy(dst, src)
}

通过在文件关闭函数之前加上defer,保证了不管什么情况下都会执行文件关闭的操作。

代码逻辑越复杂,defer使用越重要。

同理,进行网络编程时,最后也要关闭整个网络的链接,也会用到defer。

3.2  defer执行顺序

先看如下程序执行结果是:

defer fmt.Println("hello world") // 延迟调用
defer fmt.Println("I am regal")
defer fmt.Println("print 3.....")

执行的结果是:

print 3.....
I am regal
hello world

总结:如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。

如下程序执行的结果:

func Test(x int)  {
result := 100 / x
fmt.Println("Test...result = ", result)
}

func main() {
defer fmt.Println("hello world") // 延迟调用
defer fmt.Println("I am regal")
defer Test(0) // 传递0将会导致 panic 报错
defer fmt.Println("print 3.....")
}

执行结果:

print 3.....
I am regal
hello world
panic: runtime error: integer divide by zero # Test导致的panic错误

即使函数或某个延迟调用发生错误,这些调用依旧会被执⾏。

3.3  defer与匿名函数结合使用

基本用法如下:

我们先看以下程序的执行结果:

func main() {
a := 10
b := 20

defer func() {
fmt.Println("匿名函数中a", a)
fmt.Println("匿名函数中b", b)
}()

a = 100
b = 200
fmt.Println("main函数中a", a)
fmt.Println("main函数中b", b)
}

执行的结果如下:

main函数中a 100
main函数中b 200
匿名函数中a 100
匿名函数中b 200

前面讲解过,defer会延迟函数的执行,虽然立即调用了匿名函数,但是该匿名函数不会执行,等整个main( )函数结束之前在去调用执行匿名函数,所以输出结果如上所示。

现在将程序做如下修改:

func main() {
a := 10
b := 20

defer func(a, b int) {
fmt.Println("匿名函数中a", a)
fmt.Println("匿名函数中b", b)
}(a, b) // 修改传递参数 a b

a = 100
b = 200
fmt.Println("main函数中a", a)
fmt.Println("main函数中b", b)
}

该程序的执行结果如下:

main函数中a 100
main函数中b 200
匿名函数中a 10
匿名函数中b 20

从执行结果上分析,由于匿名函数前面加上了defer所以,匿名函数没有立即执行。但是问题是,程序从上开始执行当执行到匿名函数时,虽然没有立即调用执行匿名函数,但是已经完成了参数的传递。

思考:以下程序的输出结果是:

(1)阅读程序,分析结果

func f1() (r int) {
defer func() {
r++
}()
r = 0
return
}

func main() {
i := f1()
fmt.Println(i)
}

(2)阅读程序,分析结果

func double(x int) int  {
return x + x
}

func triple(x int) (r int) {
defer func() {
r += x
}()
return double(x)
}

func main() {
fmt.Println(triple(3))
}



举报

相关推荐

0 条评论