Go 有通用的控制流程:if,for,switch,goto。它也有go语句用于让代码运行在单独的协程。这里我将讨论一些不常见的问题:defer,panic 和 recover。

defer语句将函数调用推送到列表。这个保存调用的列表在函数返回后执行。defer通常用于简化执行各种清理操作。

例如,让我们看一个打开两个文件并将一个文件的内容复制到另一个文件的函数:

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
}

这个能工作,但有一个漏洞。如果调用os.Create 失败,函数将在不关闭源文件的情况下返回。这可以轻松补救,在第二个return 语句之前调用src.Close,但如果函数更复杂,则问题可能不那么容易被注意到和解决。通过引入defer语句,我们可以确保文件始终关闭:

func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close() dst, err := os.Create(dstName)
if err != nil {
return
}
defer dst.Close() return io.Copy(dst, src)
}

defer语句允许我们在打开每个文件后立即考虑关闭它,从而保证无论函数中的return语句数量是多少,这些文件都将被关闭。

defer语句的行为是简单直接且可预测的。有三个简单的规则:

1、当defer被声明时,其参数就会被计算。

在此示例中,当Println调用被defer声明,将计算表达式“i”。defer调用将在函数返回后打印“0”。

2、defer的执行顺序为先进后出。

此函数打印“3210”:

func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}

3、defer可以读取有名返回值。

在此示例中,defer函数在函数返回后递增返回值i。因此,此函数返回2:

func c() (i int) {
defer func() { i++ }()
return 1
}

这对于修改函数的错误返回值很方便;我们很快就会看到一个这样的例子。

Panic是一个内置功能,可以停止普通的控制流程并开始Panicing。当函数F调用panic时,F的执行会停止,F中的任何defer函数都正常执行,然后F返回给其调用方。对于调用方,F的表现就像是panic。该过程继续在堆栈中向上移动,直到当前协程 中的所有函数都返回,此时程序崩溃。可以通过直接调用panic来启动panic。它们也可能是由运行时错误引起的,例如越界数组访问。

recover是一个内置功能,可以重新获得对正在panic的协程的控制。recover仅在defer函数中有用。在正常执行期间,recover调用将返回nil,并且没有其他效果。如果当前协程 出现panic,则调用recover将捕获panic提供的值并恢复正常执行。

下面是一个示例程序,演示了panicdefer的机制:

package main

import "fmt"

func main() {
f()
fmt.Println("Returned normally from f.")
} func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
} func g(i int) {
if i > 3 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
defer fmt.Println("Defer in g", i)
fmt.Println("Printing in g", i)
g(i + 1)
}

函数g获取int i,如果i大于 3,则会出现panic,否则它将使用参数i+1调用自己。函数f defer调用recover并输出恢复值的函数(如果该值为非 nil)。在继续阅读之前,请尝试想象此程序的输出可能是什么。

程序将输出:

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.

如果我们从f中删除derfer函数,则不会恢复panic,并在到达协程调用堆栈顶部时终止程序。这个修改后的程序将输出:

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4 panic PC=0x2a9cd8
[stack trace omitted]

有关panicrecover的真实例子,请参阅Go标准库中的json 包。它使用一组递归函数对接口进行编码。如果在遍历该值时发生错误,则会调用panic将堆栈展开到最上层的函数调用,该调用将从panic 中恢复并返回适当的错误值(请参阅 encode.go 中 encodeState 类型的“error”和“marshal”方法)。

Go库中的约定是,即使包在内部使用panic,其对外的API仍会展示显式错误返回值。

defer的其他用法(除了前面给出的file.Close例子)还包括释放互斥锁:

mu.Lock()
defer mu.Unlock()

打印页脚:

printHeader()
defer printFooter()

以及更多。

总之,defer语句(有/没有panicrecover)为控制流提供了一种不同寻常且功能强大的机制。它可用于对其他编程语言中特殊用途结构实现的许多特性进行建模。试试吧。


原文 https://golang.google.cn/blog/defer-panic-and-recover

【译】defer-panic-and-recover的更多相关文章

  1. GOLANG错误处理最佳方案errors wrap, Defer, Panic, and Recover

    Simple error handling primitives:        https://github.com/pkg/errors Defer, Panic, and Recover:    ...

  2. 15 Defer, Panic, and Recover

    Defer, Panic, and Recover 4 August 2010 Go has the usual mechanisms for control flow: if, for, switc ...

  3. Golang 入门系列(十四)defer, panic和recover用法

    以前讲过golang 的基本语法.但是,只是讲了一些基础的语法,感兴趣的可以看看以前的文章,https://www.cnblogs.com/zhangweizhong/category/1275863 ...

  4. 6.Go-错误,defer,panic和recover

    6.1.错误 Go语言中使用builtin包下error接口作为错误类型 Go语言中错误都作为方法/函数的返回值 自定义错误类型 //Learn_Go/main.go package main imp ...

  5. go语言defer panic recover用法总结

    defer defer是go提供的一种资源处理的方式.defer的用法遵循3个原则 在defer表达式被运算的同时,defer函数的参数也会被运算.如下defer的表达式println运算的同时,其入 ...

  6. panic和recover的使用规则

    转自个人博客 chinazt.cc 在上一节中,我们介绍了defer的使用. 这一节中,我们温习一下panic和recover的使用规则. 在golang当中不存在tye ... catch 异常处理 ...

  7. Go语言异常处理defer\panic\recover

    Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱.因为开发者很容易滥用异常, ...

  8. 【Go入门教程3】流程(if、goto、for、switch)和函数(多个返回值、变参、传值与传指针、defer、函数作为值/类型、Panic和Recover、main函数和init函数、import)

    这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和 ...

  9. go语言中使用defer、panic、recover处理异常

    go语言中的异常处理,没有try...catch等,而是使用defer.panic.recover来处理异常. 1.首先,panic 是用来表示非常严重的不可恢复的错误的.在Go语言中这是一个内置函数 ...

  10. Go基础系列:defer、panic和recover

    defer关键字 defer关键字可以让函数或语句延迟到函数语句块的最结尾时,即即将退出函数时执行,即便函数中途报错结束.即便已经panic().即便函数已经return了,也都会执行defer所推迟 ...

随机推荐

  1. Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口?

    可以继承其他类或实现其他接口,在 Swing 编程和 Android 开发中常用此方式来 实现事件监听和回调.

  2. 在Java中,如何跳出当前的多重嵌套循环?

    答:在最外层循环前加一个标记如A,然后用break A;可以跳出多重循环.(Java中支持带标签的break和continue语句,作用有点类似于C和C++中的goto语句,但是就像要避免使用goto ...

  3. jQuery--子元素过滤

    1.子元素过滤器介绍 :nth-child 获得指定索引的孩子,从1开始 :first-child 获得第一个孩子 :last-child 获得最后一个孩子 :only-child 获得独生子 2.代 ...

  4. JVM组成结构以及各部分的功能

    Java虚拟机主要分为以下五个区: 一.方法区(METHOD AREA): 1. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型 ...

  5. 你将如何使用 thread dump?你将如何分析 Thread dump?

    新建状态(New) 用 new 语句创建的线程处于新建状态,此时它和其他 Java 对象一样,仅仅在堆区 中被分配了内存. 就绪状态(Runnable) 当一个线程对象创建后,其他线程调用它的 sta ...

  6. 遇到的问题之“解决tomcat中文乱码问题”

    方案1:在server.xml中添加了 URIEncoding="UTF-8" 属性 路径:C:\stop\apache-tomcat-8.5.69\conf 修改内容 如若方案1 ...

  7. SVN回滚步骤

  8. 让弹幕给 PPD 生个孩子

    怎样才能跑起来我写的弹幕程序 资源下载 申请野狗后端云账号注册 创建应用: 复制appId到index.html的 var ref = new Wilddog("https://<ap ...

  9. 夏日葵电商:连锁零售店小程序o2o系统解决方案

    公众平台"附近小程序"功能上线后,一个主体账号可以同时绑定N+个门店,这对连锁零售店铺来说是重磅福利呀,无论你是通过搜索还是线下扫码进入小程序,线上与线下都完全贯通了,线上多种入口 ...

  10. 【每日日报】第四十七天---<div>

    1 学习HTML HTML <div> 元素是块级元素,它可用于组合其他 HTML 元素的容器. <div> 元素没有特定的含义.除此之外,由于它属于块级元素,浏览器会在其前后 ...