异常处理
defer 语句
在Go语言中,defer语句用于延迟函数的执行,直到包含它的函数返回时才执行。这意味着被 defer 修饰的函数调用会在当前函数的所有其他操作完成后执行,即使遇到 panic 或者直接返回,也会执行。
defer的主要特点:
- 延迟执行
- defer语句的作用就是将一个函数调用延迟到所在函数的结束时执行
- 执行顺序
- 如果一个函数中有多个defer语句,它们会按照后进先出的顺序执行。即最后一个defer语句最先执行,第一个defer语句最后执行
- 参数的评估时机
- defer声明时,函数的参数会立即评估,但函数体不会执行。也就是延迟执行的函数会记住当时的参数值和环境,而不是在执行时再计算参数
使用场景:
- 资源释放
- defer常用于在函数结束时自动执行一些清理操作,如关闭文件、释放锁、回滚事务等,确保资源被正确释放
- 异常处理
- 在处理异常或错误恢复时,defer可以用来在panic之后执行一些恢复操作
- 函数退出的操作
- 一些在函数退出时需要执行的操作,如日志记录、计时等
package main
import "fmt"
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y)) // 其内函数会立即求值,calc("A",x,y)会先执行
x = 10
defer calc("BB", x, calc("B", x, y)) // 同理calc("B",x,y)也会先执行
y = 20
}
/* 输出结果:
A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4
本例中,calc("A", x, y) 和 calc("B", x, y) 的结果在 defer 语句声明时就已经计算并保存下来了,即使之后 x 和 y 的值发生了变化。
*/
panic和recover
在Go语言中,panic和recover是用于异常处理的两个关键字,提供了一种机制来应对程序中的严重错误或意外情况。
panic语句用于中止程序的正常执行。它通常在发生无法恢复的错误时使用,或者在程序遇到严重问题时调用。
- 调用panic后,程序的控制流会立即终端,当前函数的执行也会停止
- Go语言会沿着调用栈逐层向上传递panic,并在每一层执行defer语句
- 如果panic传递到main函数,程序将崩溃并退出,并打印panic消息及堆栈跟踪
常用于捕获不可恢复的错误,如数组越界、空指针引用等。
recover是用来从panic中恢复程序执行的。它只在延迟函数(即defer中)内有效。
- 当panic发生时,如果调用了recover,panic的传播就会停止,程序从引发panic的地方恢复正常执行
- recover返回panic的值,如果没有panic发生,则返回nil
recover通常用于创建容错机制,使程序能够从不可恢复的错误中恢复过来,而不会崩溃。
在异常源头捕获:
package main
import "fmt"
func funcA() {
defer func() {
//如果程序出出现了panic错误,可以通过recover恢复过来
if err := recover(); err != nil {
fmt.Printf("%v\n", err)
}
}()
panic("出错了")
}
func main() {
fmt.Println("main函数开始执行")
funcA()
fmt.Println("main函数结束执行")
}
// 输出结果
main函数开始执行
出错了
main函数结束执行
如果不在异常抛出的源头-函数funcA()中捕获异常,而在其调用者中捕获,效果也完全相同。因为funcA()中抛出的异常会自动向上传播,所以在main()函数中同样可以捕获。
package main
import "fmt"
func funcA() {
panic("出错了")
}
func main() {
defer func() {
//如果程序出出现了panic错误,可以通过recover恢复过来
if err := recover(); err != nil {
fmt.Printf("%v\n", err)
}
}()
fmt.Println("main函数开始执行")
funcA()
fmt.Println("main函数结束执行")
}
// 输出结果
main函数开始执行
出错了
将defer+recover捕获异常的代码迁移到main()函数中。执行该代码段,同样能够实现异常捕获。但需要注意的是,一旦产生异常,同样会打断程序执行,并马上进入defer执行环节。因此,此处不会输出字符串“main函数结束执行”。
虽然在上层函数中同样可以捕获异常,但也要特别注意它与在异常源头捕获的区别。