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

2021-09-17 10:23:54 浏览数 (1)

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

3 延迟调用defer

3.1 defer基本使用

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

代码语言:javascript复制
fmt.Println("hello world")
fmt.Println("I am regal") 

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

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

基本用法如下:
代码语言:javascript复制
defer fmt.Println("hello world") // 延迟调用
fmt.Println("I am regal")
fmt.Println("print 3.....")

执行如下:

代码语言:javascript复制
I am regal
print 3.....
hello world # 最后延迟调用
defer的应用场景:

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

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

代码语言:javascript复制
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 
} 

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

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

代码语言:javascript复制
if err != nil {
   return
}

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

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

代码语言:javascript复制
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执行顺序

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

代码语言:javascript复制
defer fmt.Println("hello world") // 延迟调用
defer fmt.Println("I am regal")
defer fmt.Println("print 3.....") 

执行的结果是:

代码语言:javascript复制
print 3.....
I am regal
hello world

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

如下程序执行的结果:

代码语言:javascript复制
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.....")
} 

执行结果:

代码语言:javascript复制
print 3.....
I am regal
hello world
panic: runtime error: integer divide by zero # Test导致的panic错误

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

3.3 defer与匿名函数结合使用

基本用法如下:

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

代码语言:javascript复制
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)
} 

执行的结果如下:

代码语言:javascript复制
main函数中a 100
main函数中b 200
匿名函数中a 100
匿名函数中b 200 

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

现在将程序做如下修改:

代码语言:javascript复制
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)
} 

该程序的执行结果如下:

代码语言:javascript复制
main函数中a 100
main函数中b 200
匿名函数中a 10
匿名函数中b 20 

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

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

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

代码语言:javascript复制
func f1() (r int) {
   defer func() {
      r  
   }()
   r = 0
   return
}

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

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

代码语言:javascript复制
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 人点赞