go有一些常用的控制流语句:if
、for
、switch
、goto
。另外还有go关键词在不同的协程间调用代码。这篇文章讲解不太常用到的一个:defer
,panic
和recover
defer
关键词将方法调用存在列表里。列表内的方法会在父方法返回结果时调用。defer
通常用在一些方法内的清理操作。
例如,下面的方法打开两个文件,同时把一个内容复制到另一个文件内:
func CopyFile(dstName,srcNmae 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
}
这个方法可以运行,但是有一个bug,如果os.Create()
方法调用失败,方法退出的时候源文件并没有正常的关闭。虽然可以在return
前加一个源文件的关闭方法来解决,但是当一个方法特别复杂的时候问题就没那么容易发现和解决了。所以,通过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
语法容许我们在每个文件打开的时候就考虑关掉文件,保证不论程序在什么情况下退出,文件都能正常关闭。
defer
的使用非常直接了当,但是他有三个简单的规则:
defer
定义的方法得变量在定义的时候就赋值了
下例中,i
在输出的时候赋值是0,所以即使后面i++输出结果也没有改变
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
defer
调用遵循后进先出的顺序执行
下面结果一次打印3210
func b() {
for i:=0;i<4;i++ {
defer fmt.Print(i)
}
}
defer
方法会对返回结果进行读取赋值
下面例子中,defer
方法在return
之后读取并将变量i
自增,所以方法返回结果为2
func c() (i int) {
defer func(){ i ++} ()
return 1
}
这对于确认方法的错误返回结果很方便。我们马上会看到另外一个例子。
panic
是用控制程序控制流的内置方法。当方法F
调用panic
,F
的执行停止,F
内的defer
方法会正常调用,流程返回到调用F
的位置。panic
可直接调用,也可因运行错误(如数组越界)自动调用。
recover
方法重新获取退出的控制流。它只在defer
方法内有用。在正常执行过程中,recover
会返回nil。程序崩溃时,recover
会捕获到错误并执行正常操作。
下例是演示panic
和defer
如何操作
package main
import "fmt"
func main() {
f()
fmt.Println("return 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("return normal 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
方法获取参数i
,如果i
大于3的时候调用panic
方法,同时递归执行i+1
.f
方法定义了defer
和recover
,打印recover
的返回结果。在看结果钱猜测下可能输出什么。
程序输出为:
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
return normally from f
如果把f
函数中的defer
去掉,程序输出结果则为
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
的实际用例,可以查看json.package
标准库。定义了一系列递归方法的接口,如果遍历结果中发生错误,panic
会释放到底层调用的堆栈,recover
会返回正确的错误。(查看error和marshal方法编码状态在encode.go)
在go类库的惯例中,如果一个包内部使用了panic
,外部调用的接口也会显式的展示返回结果。
defer
的其他用法(除了关闭文件和之前的一些)也包含释放互斥锁
mu.Lock()
defer mu.Unlock()
打印footer:
printHeader()
defer printFooter()
等等
总的来说,defer(panic和recover)提供了一个非凡且功能强大的流程控制能力