Go基础系列:defer、panic和recover
defer关键字
defer关键字可以让函数或语句延迟到函数语句块的最结尾时,即即将退出函数时执行,即便函数中途报错结束、即便已经panic()、即便函数已经return了,也都会执行defer所推迟的对象。
其实defer的本质是,当在某个函数中使用了defer关键字,则创建一个独立的defer栈帧,并将该defer语句压入栈中,同时将其使用的相关变量也拷贝到该栈帧中(显然是按值拷贝的)。因为栈是LIFO方式,所以先压栈的后执行。因为是独立的栈帧,所以即使调用者函数已经返回或报错,也一样能在它们之后进入defer栈帧去执行。
例如:
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的对象也会在函数退出前的最后一刻执行。
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吗?先再改成下面的:
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)
是相同的。
原因在于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()和recover()
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().
Go基础系列:defer、panic和recover的更多相关文章
- GOLANG错误处理最佳方案errors wrap, Defer, Panic, and Recover
Simple error handling primitives: https://github.com/pkg/errors Defer, Panic, and Recover: ...
- 15 Defer, Panic, and Recover
Defer, Panic, and Recover 4 August 2010 Go has the usual mechanisms for control flow: if, for, switc ...
- Golang 入门系列(十四)defer, panic和recover用法
以前讲过golang 的基本语法.但是,只是讲了一些基础的语法,感兴趣的可以看看以前的文章,https://www.cnblogs.com/zhangweizhong/category/1275863 ...
- 6.Go-错误,defer,panic和recover
6.1.错误 Go语言中使用builtin包下error接口作为错误类型 Go语言中错误都作为方法/函数的返回值 自定义错误类型 //Learn_Go/main.go package main imp ...
- go语言defer panic recover用法总结
defer defer是go提供的一种资源处理的方式.defer的用法遵循3个原则 在defer表达式被运算的同时,defer函数的参数也会被运算.如下defer的表达式println运算的同时,其入 ...
- panic和recover的使用规则
转自个人博客 chinazt.cc 在上一节中,我们介绍了defer的使用. 这一节中,我们温习一下panic和recover的使用规则. 在golang当中不存在tye ... catch 异常处理 ...
- Go语言异常处理defer\panic\recover
Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱.因为开发者很容易滥用异常, ...
- 【Go入门教程3】流程(if、goto、for、switch)和函数(多个返回值、变参、传值与传指针、defer、函数作为值/类型、Panic和Recover、main函数和init函数、import)
这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和 ...
- go语言中使用defer、panic、recover处理异常
go语言中的异常处理,没有try...catch等,而是使用defer.panic.recover来处理异常. 1.首先,panic 是用来表示非常严重的不可恢复的错误的.在Go语言中这是一个内置函数 ...
随机推荐
- vue学习-自动行合并的table
测试的效果 测试的html源码截图 v-table在tableGroup.js中定义,以下就render方法,行的所有单元格都在tableGrouper.js中处理 render:function(h ...
- Chrome 的 PNaCl 还活着么?
WebAssembly Migration Guide Given the momentum of cross-browser WebAssembly support, we plan to focu ...
- poj 1026
这题一开始没看清楚 等级差距不超过1 1->2->3 就是错误的,因为3-1==2 ,意思是间接的也不行 其次等级最小是1,最大是n 你要到达1号首领的位置 假设1号等级x,限制m,最大上 ...
- iOS 数据归档----温故而知新
#import "StudyViewController.h" #import "person.h" @interface StudyViewControlle ...
- Spring Boot 2 - 使用CommandLineRunner与ApplicationRunner
本篇文章我们将探讨CommandLineRunner和ApplicationRunner的使用. 在阅读本篇文章之前,你可以新建一个工程,写一些关于本篇内容代码,这样会加深你对本文内容的理解,关于如何 ...
- Python编程练习:使用 turtle 库完成玫瑰花的绘制
绘制效果: 源代码: import turtle # 设置初始位置 turtle.penup() turtle.left(90) turtle.fd(200) turtle.pendown() tur ...
- 【2016年终大典】i春秋一年中不可错过的安全精华
这是一个24小时不下课的安全技术大学堂, 每分钟250条学习状态发布, 每天迎接3万求知若渴的用户, 最高同时在线人数超过2万人: 这是一个知识分享的聚宝盆, 安全技术课程208门.2138节.427 ...
- 基于APNs最新HTTP/2接口实现iOS的高性能消息推送(服务端篇)
1.前言 本文要分享的消息推送指的是当iOS端APP被关闭或者处于后台时,还能收到消息/信息/指令的能力. 这种在APP处于后台或关闭情况下的消息推送能力,通常在以下场景下非常有用: 1)IM即时通讯 ...
- Android WebView 实现网页缩放
需要对WebView设置的内容如下: //缩放开关,设置此属性,仅支持双击缩放,不支持触摸缩放 mWebView.getSettings().setSupportZoom(true); //设置是否可 ...
- 吴恩达机器学习笔记34-模型选择和交叉验证集(Model Selection and Train_Validation_Test Sets)
假设我们要在10 个不同次数的二项式模型之间进行选择: 显然越高次数的多项式模型越能够适应我们的训练数据集,但是适应训练数据集并不代表着能推广至一般情况,我们应该选择一个更能适应一般情况的模型.我们需 ...