defer关键字可让函数或语句延迟到函数语句块的最结尾时,即即将退出函数时执行,即使函数中途报错结束、即使已经panic()、即使函数已经return了,也都会执行defer所推迟的对象。安全
其实defer的本质是,当在某个函数中使用了defer关键字,则建立一个独立的defer栈帧,并将该defer语句压入栈中,同时将其使用的相关变量也拷贝到该栈帧中(显然是按值拷贝的)。由于栈是LIFO方式,因此先压栈的后执行。由于是独立的栈帧,因此即便调用者函数已经返回或报错,也同样能在它们以后进入defer栈帧去执行。函数
例如:ui
func main() { a() } func a() { println("in a") defer b() // 将b()压入defer栈中 println("leaving a") //到了这里才会执行b() } func b() { println("in b") println("leaving b") }
上面将输出:指针
in a leaving a in b leaving b
即使是函数已经报错,或函数已经return返回,defer的对象也会在函数退出前的最后一刻执行。code
func a() TYPE{ ...CODE... defer b() ...CODE... // 函数执行出了错误 return args // 函数b()都会在这里执行 }
但注意,因为Go的做用域采用的是词法做用域,defer的定义位置决定了它推迟对象能看见的变量值,而不是推迟对象被调用时所能看见的值。对象
例如:资源
package main var x = 10 func main() { a() } func a() { println("start a:",x) // 输出10 x = 20 defer b(x) // 压栈,并按值拷贝20到栈中 x = 30 println("leaving a:",x) // 输出30 // 调用defer延迟的对象b(),输出20 } func b(x int) { println("start b:",x) }
比较下面的defer:作用域
package main var x = 10 func main() { a() } func a() int { println("start a:", x) // 输出10 x = 20 defer func() { // 压栈,但并未传值,因此内部引用x println("in defer:", x) // 输出30 }() x = 30 println("leaving a:", x) // 输出30 return x }
上面defer推迟的匿名函数输出的值是30,它看见的不该该是20吗?先再改为下面的:it
package main var x = 10 func main() { a() } func a() int { println("start a:", x) // 输出10 x = 20 defer func(x int) { println("in defer:", x) // 输出20 }(x) x = 30 println("leaving a:", x) // 输出30 return x }
这个defer推迟的对象中看见的倒是20,这和第一种defer b(x)
是相同的。ast
缘由在于defer推迟的若是是函数,它直接就在它的定义位置处评估好参数、变量。该拷贝传值的拷贝传值,该指针相见的指针相见。因此,对于第(1)和第(3)种状况,在defer的定义位置处,就将x=20拷贝给了推迟的函数参数,因此函数内部操做的一直是x的副本。而第二种状况则是直接指向它所看见的x=20那个变量,则个变量是全局变量,当执行x=30的时候会将其值修改,到执行defer推迟的对象时,它指向的x的值已是修改过的。
再看下面这个例子,将defer放进一个语句块中,并在这个语句块中新声明一个同名变量x:
func a() int { println("start a:", x) // 输出10 x = 20 { x := 40 defer func() { println("in defer:", x) // 输出40 }() } x = 30 println("leaving a:", x) // 输出30 return x }
上面的defer定义在语句块中,它能看见的x是语句块中x=40
,它的x指向的是语句块中的x。另外一方面,当语句块结束时,x=40
的x会消失,但因为defer的函数中仍有x指向40这个值,因此40这个值仍被defer的函数引用着,它直到defer执行完以后才会被GC回收。因此defer的函数在执行的时候,仍然会输出40。
若是语句块内有多个defer,则defer的对象以LIFO(last in first out)的方式执行,也就是说,先定义的defer后执行。
func main() { println("start...") defer println("1") defer println("2") defer println("3") defer println("4") println("end...") }
将输出:
start... end... 4 3 2 1
defer有什么用呢?通常用来作善后操做,例如清理垃圾、释放资源,不管是否报错都执行defer对象。另外一方面,defer可让这些善后操做的语句和开始语句放在一块儿,不管在可读性上仍是安全性上都颇有改善,毕竟写完开始语句就能够直接写defer语句,永远也不会忘记关闭、善后等操做。
例如,打开文件,关闭文件的操做写在一块儿:
open() defer file.Close() ... 操做文件 ...
如下是defer的一些经常使用场景:
panic()用于产生错误信息并终止当前的goroutine,通常将其看做是退出panic()所在函数以及退出调用panic()所在函数的函数。例如,G()中调用F(),F()中调用panic(),则F()退出,G()也退出。
注意,defer关键字推迟的对象是函数最后调用的,即便出现了panic也会调用defer推迟的对象。
例如,下面的代码中,main()中输出一个start main
以后调用a(),它会输出start a
,而后就panic了,panic()会输出panic: panic in a
,而后报错,终止程序。
func main() { println("start main") a() println("end main") } func a() { println("start a") panic("panic in a") println("end a") }
执行结果以下:
start main start a panic: panic in a goroutine 1 [running]: main.a() E:/learning/err.go:14 +0x63 main.main() E:/learning/err.go:8 +0x4c exit status 2
注意上面的end a
和end main
都没有被输出。
可使用recover()去捕获panic()并恢复执行。recover()用于捕捉panic()错误,并返回这个错误信息。但注意,即便recover()捕获到了panic(),但调用含有panic()函数的函数(即上面的G()函数)也会退出,因此若是recover()定义在G()中,则G()中调用F()函数以后的代码都不会执行(见下面的通用格式)。
如下是比较通用的panic()和recover()的格式:
func main() { G() // 下面的代码会执行 ...CODE IN MAIN... } func G(){ defer func (){ if str := recover(); str != nil { fmt.Println(str) } }() ...CODE IN G()... // F()的调用必须在defer关键字以后 F() // 该函数内下面的代码不会执行 ...CODE IN G()... } func F() { ...CODE1... panic("error found") // 下面的代码不会执行 ...CODE IN F()... }
可使用recover()去捕获panic()并恢复执行。但如下代码是错误的:
func main() { println("start main") a() println("end main") } func a() { println("start a") panic("panic in a") // 直接放在panic后是错误的 panic_str := recover() println(panic_str) println("end a") }
之因此错误,是由于panic()一出现就直接退出函数a()和main()了。要想recover()真正捕获panic(),须要将recover()放在defer的推迟对象中,且defer的定义必须在panic()发生以前。
例如,下面是通用格式的示例:
package main import "fmt" func main() { println("start main") b() println("end main") } func a() { println("start a") panic("panic in a") println("end a") } func b() { println("start b") defer func() { if str := recover(); str != nil { fmt.Println(str) } }() a() println("end b") }
如下是输出结果:
start main start b start a panic in a end main
注意上面的end b
、end a
都没有被输出,可是end main
输出了。
panic()是内置的函数(在包builtin中),在log
包中也有一个Panic()函数,它调用Print()输出信息后,再调用panic()。go doc log Panic
一看便知:
$ go doc log Panic func Panic(v ...interface{}) Panic is equivalent to Print() followed by a call to panic().