上一篇: [1438164538421]
ToC
- Go 边看边练 -《Go 学习笔记》系列(一)- 变量、常量
- Go 边看边练 -《Go 学习笔记》系列(二)- 类型、字符串
- Go 边看边练 -《Go 学习笔记》系列(三)- 指针
- Go 边看边练 -《Go 学习笔记》系列(四)- 控制流 1
- Go 边看边练 -《Go 学习笔记》系列(五)- 控制流 2
- Go 边看边练 -《Go 学习笔记》系列(六)- 函数
- Go 边看边练 -《Go 学习笔记》系列(七)- 错误处理
- Go 边看边练 -《Go 学习笔记》系列(八)- 数组、切片
- Go 边看边练 -《Go 学习笔记》系列(九)- Map、结构体
- Go 边看边练 -《Go 学习笔记》系列(十)- 方法
- Go 边看边练 -《Go 学习笔记》系列(十一)- 表达式
- Go 边看边练 -《Go 学习笔记》系列(十二)- 接口
- Go 边看边练 -《Go 学习笔记》系列(十三)- Goroutine
- Go 边看边练 -《Go 学习笔记》系列(十四)- Channel
3.5 延迟调用
关键字 defer
用于注册延迟调用。这些调用直到 return
前才被执行,通常⽤用于释放资源或错误处理。
1func test() error {
2 f, err := os.Create("test.txt")
3 if err != nil { return err }
4
5 defer f.Close() // 注册调用,而不是注册函数。必须提供参数,哪怕为空。
6
7 f.WriteString("Hello, World!")
8 return nil
9}
多个 defer
注册,按 FILO 次序执行。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。
延迟调用参数在注册时求值或复制,可用指针或闭包 "延迟" 读取。
滥用 defer
可能会导致性能问题,尤其是在一个 "大循环" 里。
1var lock sync.Mutex
2 func test() {
3 lock.Lock()
4 lock.Unlock()
5}
6
7func testdefer() {
8 lock.Lock()
9 defer lock.Unlock()
10}
11
12func BenchmarkTest(b *testing.B) {
13 for i := 0; i < b.N; i++ {
14 test()
15 }
16}
17
18func BenchmarkTestDefer(b *testing.B) {
19 for i := 0; i < b.N; i++ {
20 testdefer()
21 }
22}
输出:
1BenchmarkTest" 50000000 43 ns/op
2BenchmarkTestDefer 20000000 128 ns/op
3.6 错误处理
没有结构化异常,使用 panic
抛出错误,recover
捕获错误。
1func test() {
2 defer func() {
3 if err := recover(); err != nil {
4 println(err.(string)) // 将 interface{} 转型为具体类型。
5 }
6 }()
7
8 panic("panic error!")
9}
由于 panic
、recover
参数类型为 interface{}
,因此可抛出任何类型对象。
1func panic(v interface{})
2func recover() interface{}
延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获。
1func test() {
2 defer func() {
3 fmt.Println(recover())
4 }()
5
6 defer func() {
7 panic("defer panic")
8 }()
9
10 panic("test panic")
11}
12
13func main() {
14 test()
15}
输出:
1defer panic
捕获函数 recover
只有在延迟调用内直接调用才会终止错误,否则总是返回 nil
。任何未捕获的错误都会沿调用堆栈向外传递。
1func test() {
2 defer recover() // 无效!
3 defer fmt.Println(recover()) // 无效!
4 defer func() {
5 func() {
6 println("defer inner")
7 recover() // 无效!
8 }()
9 }()
10
11 panic("test panic")
12}
13
14func main() {
15 test()
16}
输出:
1defer inner
2<nil>
3panic: test panic
使用延迟匿名函数或下面这样都是有效的。
1func except() {
2 recover()
3}
4
5func test() {
6 defer except()
7 panic("test panic")
8}
如果需要保护代码片段,可将代码块重构成匿名函数,如此可确保后续代码被执行。
1func test(x, y int) {
2 var z int
3
4 func() {
5 defer func() {
6 if recover() != nil { z = 0 }
7 }()
8
9 z = x / y
10 return
11 }()
12
13 println("x / y =", z)
14}
除用 panic
引发中断性错误外,还可返回 error
类型错误对象来表示函数调用状态。
1type error interface {
2 Error() string
3}
标准库 errors.New
和 fmt.Errorf
函数用于创建实现 error
接口的错误对象。通过判断错误对象实例来确定具体错误类型。
如何区别使用 panic
和 error
两种方式?惯例是:导致关键流程出现不可修复性错误的使用 panic
,其他使用 error
。
下一篇: [1438311936449]
- 本系列是基于雨痕的《Go 学习笔记》(第四版)整理汇编而成,非常感谢雨痕的辛勤付出与分享!
- 转载请注明:文章转载自:黑客与画家的社区 [http://symphony.b3log.org]
- 如果你觉得本章节做得不错,请在下面打赏一下吧~
社区小贴士
- 关注标签 [golang] 可以方便查看 Go 相关帖子
- 关注标签 [Go 学习笔记] 可以方便查看本系列
- 关注作者后如有新帖将会收到通知
该文章同步自 黑客派