内容简介:翻译自:Golang的常用控制流程机制有:if,for,switch,goto,他还有go语句启动goroutine运行代码。在这里,我跟大家讨论一些不太常用的:defer,panic,recoverdefer 语句会将一个函数添加到函数调用列表,等defer所在的函数返回时,会调用这个列表的所有函数(后进先出的方式)。延迟通常用于简化执行各种清理操作。
翻译自: https://blog.golang.org/defer-panic-and-recover
Golang的常用控制流程机制有:if,for,switch,goto,他还有 go 语句启动goroutine运行代码。在这里,我跟大家讨论一些不太常用的:defer,panic,recover
defer 语句会将一个函数添加到函数调用列表,等defer所在的函数返回时,会调用这个列表的所有函数(后进先出的方式)。延迟通常用于简化执行各种清理操作。
例如,让我们看一个打开两个文件并将其中一个文件的内容拷贝到另一个文件的函数:
func CopyFile(dstName, srcName string) (written uint64, 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 }
这虽然能正常运行,但是存在bug。如果os.Create失败,那么该函数会在没有关闭src文件就返回了。虽然这很容易通过在第二个return语句之前调用src.Close()来解决这个Bug,但是如果函数更复杂,则问题可能不会轻易被注意到并得到解决。通过使用defer语句,我们能够确保文件始终被关闭
func CopyFile(dstName, srcName string) (written uint64, 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语句的行为是直接了当并且可以预测的。他有三个简单的规则:
-
defer语句被评估时,被defer语句修饰的函数的参数也会被评估。
在此示例中,Println函数被defer修饰时就会计算表达式"i",函数返回后,延迟调用将会输出“0”
func a() { i := 0 defer fmt.Prinln(i) i++ return }
-
在函数返回后,延迟调用函数按照后进先出的方式被调用.
函数b将会输出3210
func b() { for i := 0; i < 4; i++ { defer fmt.Print(i) } }
-
延迟函数可以读取和修改函数的命名返回值
此示例中,defer修饰的函数会在周围函数放回后会修改返回值。因此,函数返回"2"
func c() (i int) { defer func() {i++} () return i }
panic是一个内置函数,他能打断当前普通的控制留,并开始panicing。当函数F调用panic时,F的执行就会停止。F函数中在panic之前用defer语句声明的函数都将会正常执行。然后F返回给他的调用者。对于调用F函数的函数来说,F函数就表现的像panic函数一样。这个表现会一直向上抛,直到当前goroutine的所有函数都返回了,此时程序崩溃。Panics可由直接调用panic函数引起,也可以由运行时错误引起。例如:数据越界访问等。
recover是一个内置函数,可以控制goroutin的panic。recover仅在defer语句修饰的函数内有用。在正常的函数执行中,调用recover会返回nil并且没有其他任何效果。如果当前的goroutine正在发生panicing,在defer修饰的函数中调用recover能够捕获panic的值并且恢复正常执行。
下面是一个演示panic和recover机制的示例程序
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时触发panics,否则,传递参数i+1来递归调用自己。函数f用defer声明了一个函数,函数中调用recover并打印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中的defer修饰的函数。则此panic不会被捕获,并会等达到当前goroutine的调用堆栈顶部,终止程序。此修改后的程序输出如下:
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]
有关panic和recover的真实示例,请参考Go标准库里的 json 包,他使用一系列的递归函数对json编码的数据进行解码,当遇到错误的json格式时,解析器会调用panic将堆栈展开到顶级函数调用,该函数recover这panic并获得这个err值。
go库中约定,即使在包内使用panic时,其外部的API还是会显示明确的错误返回值。
defer的其他用户还有很多,包括释放锁:
mu.Lock() defer mu.Unlock()
输出HtmlFooter
printHeader() defer printFooter()
总之defer语句(包括使用或不实用panic和recover)提供了强大的控制流程的机制。他能模拟
其他语言中的专用结构实现的许多功能。你试试看。
By Andrew Gerrand
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- defer,panic 和 Recover
- Go语言的defer, panic和recover
- Go的Defer、Panic和Recover (翻译)
- Go语言开发-过程式编程-Defer,Panic和Recover
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深入理解C++11
Michael Wong、IBM XL编译器中国开发团队 / 机械工业出版社 / 2013-6 / 69.00元
《深入理解C++11:C++11新特性解析与应用》内容简介:国内首本全面深入解读C++11新标准的专著,由C++标准委员会代表和IBM XL编译器中国开发团队共同撰写。不仅详细阐述了C++11标准的设计原则,而且系统地讲解了C++11新标准中的所有新语言特性、新标准库特性、对原有特性的改进,以及如何应用所有这些新特性。 《深入理解C++11:C++11新特性解析与应用》一共8章:第1章从设计......一起来看看 《深入理解C++11》 这本书的介绍吧!