Defer, Panic 和 Recover 这三个概念是 Golang 中独有的, 也是最基础的, 因此有必要将他们弄懂.
Golang Blog 上有一篇 Defer, Panic, and 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
失败了, 函数会直接退出, 而文件没有正常关闭.
这个问题是可以修正的, 例如可以对两个错误判断的 if 里面都加上文件关闭操作.
但是并不是所有的 Bug 都这么显而易见, 这就是 defer 引入语法索要解决的问题.
通过使用 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.被 deferred 的函数的参数在 defer 语句被求值时进行求值:
func a() { i := 0 defer fmt.Println(i) i++ return }
在本例中, defer fmt.Println(i)
中 i 的值取的是 1.
func b() { for i := 0; i < 4; i++ { defer fmt.Print(i) } }
在上例中, 会先输出 3, 再 2, 再 1, 0.
3.被 deferred 的函数可以读写函数的有名字的返回值:
func c() (i int) { defer func() { i++ }() return 1 }
在上例中, 这个函数的返回值不是 1, 而是 2.