golang基础(37.defer 语句及使用示例)
在go中当我们使用完某个资源需要将其释放,比如(网络连接,文件句柄),或者在代码运行过程中抛出错误时执行一段兜底逻辑,要怎么做呢?通过 defer 关键字声明兜底执行或者释放资源的语句可以轻松解决这个问题。比如我们看 Go 内置的 io/ioutil 包中提供的读取文件方法 ReadFile 实现源码,其中就有 defer 语句的使用:
func ReadFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
var n int64 = bytes.MinRead
if fi, err := f.Stat(); err == nil {
if size := fi.Size() + bytes.MinRead; size > n {
n = size
}
}
return readAll(f, n)
}
defer 修饰的 f.Close() 方法会在函数执行完成后或读取文件过程中抛出错误时执行,以确保已经打开的文件资源被关闭,从而避免内存泄露。如果一条语句干不完清理的工作,也可以在 defer 后加一个匿名函数来执行对应的兜底逻辑:
defer func() {
// 执行复杂的清理工作...
} ()
另外,一个函数/方法中可以存在多个 defer 语句,defer 语句的调用顺序遵循先进后出的原则,即最后一个 defer 语句将最先被执行,相当于「栈」结构,如果在循环语句中包含了 defer 语句,则对应的 defer 语句执行顺序依然符合先进后出的规则。由于 defer 语句的执行时机和调用顺序,所以我们要尽量在函数/方法的前面定义它们,以免在后面执行时漏掉,尤其是运行时抛出错误会中断后面代码的执行,也就感知不到后面的 defer 语句。下面我们看一个简单的示例 defer.go:
package main
import "fmt"
func printError() {
fmt.Println("兜底执行")
}
func main() {
defer printError()
defer func() {
fmt.Println("除数不能是0!")
}()
var i = 1
var j = 1
var k = i / j
fmt.Printf("%d / %d = %d
", i, j, k)
}
这段代码中,我们定义了两个 defer 语句,并且是在函数最顶部,以确保异常情况下也能执行。在函数正常执行的情况下,这两个 defer 语句会在最后一条打印语句执行完成后先执行第二条 defer 语句,再执行第一条 defer 语句。