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))
}