Defer
Go的控制流有一些常用的机制:if
, for
, switch
, goto
. 它也拥有可以在独立的goroutine中运行代码的go语句。接下来会介绍通常比较少用到的类型:defer
, panic
, recover
.
defer
语句将函数调用压入一个列表。当包围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
}
上面的代码可以工作,但是会有一个Bug。如果调用os.Create失败,这个函数就会立即返回却没有关闭源文件。当然,可以在出现error的地方加上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 语句的行为非常直接,可以预测。有三个简单的规则:
-
defer语句指定的函数中的变量是当执行到defer语句的时候就确定了的;
在下面的例子中,表达式“i”的值当Println被defer的时候就确定了的,所以下面的输出是0,而不是1func 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函数执行,并且将返回值加1,所以这个函数返回的是2.func c() (i int) { defer func() {i++} () return 1 }