一、defer 语句
Go 中的 defer 语句又叫做延迟执行语句,也就是说 defer 语句会将其后面根素的语句进行延迟处理。
多个 defer 语句的执行顺序
在 defer 关键字所在的函数即将返回时(返回前),将延迟处理的语句按 defer 的逆序进行执行,既先被 defer 的语句最后执行,最后被 defer 的语句最先执行,在被 defer 修饰时遵循 先进后出 原则.
package main import "fmt" func main() { fmt.Println("defer 修饰语句的执行顺序为 `先进后出`") defer fmt.Println("第一个进入 defer 栈,最会出(执行)") defer fmt.Println("第二个进入 defer 栈,倒数第二个出(执行)") defer fmt.Println("第三个进入 defer 栈,倒数第三个出(执行)") fmt.Println("defer 语句会在所属函数返回前执行:") } 复制代码
执行上述代码,输出结果如下:
defer 修饰语句的执行顺序为 `先进后出` defer 语句会在所在函数返回前执行: 第三个进入 defer 栈,倒数第三个出(执行) 第二个进入 defer 栈,倒数第二个出(执行) 第一个进入 defer 栈,最会出(执行) 复制代码
二、defer 语句在函数退出时释放资源
在文件处理和连接数据库时都会遇到 打开 和 关闭 这种成对的操作,Go 中没有 try-catch-finally 语句可以在执行 finally 代码块中执行关闭操作。
Go 中可以通过 defer 语句来代替 finally 实现资源关闭的操作,同时也能解决 try-catch-finally 的大量嵌套。
以文件打开和关闭为例,首先创建一个 info.txt 文件,Go 中打开和关闭文件需要使用到 os 包中的 Open 和 Close 函数。
package main import ( "fmt" "log" "os" ) func main() { fmt.Println(getFileSize("info.txt")) # 90 } func getFileSize(path string) (size int) { f, err := os.Open(path) if err != nil { log.Fatal(err) } // 延迟关闭文件 defer f.Close() fileInfo, err := f.Stat() if err != nil { log.Fatal(err) } size = int(fileInfo.Size()) return } 复制代码
需要注意的是 defer 后的表达式必须是函数调用。
三、defer 机制的细节
defer 语句执行时的拷贝机制,既会执行目标函数的副本,当原函数改变后,defer 执行不受影响。
func main() { zulu := func() { fmt.Println("Go") } defer zulu() zulu = func() { fmt.Println("Elixir") } } 复制代码
执行上述代码,输出结果如下:
Go 复制代码
defer 后的函数入参为值类型时,会将参数一同拷贝,修改参数不会影响 defer 后函数的执行结果。
func main() { x := 6 zulu := func(x int) { fmt.Println(x) fmt.Println("Go") } defer zulu(x) x = 16 } 复制代码
执行上述代码,输出结果如下:
6 Go 复制代码
将 defer 后 zulu 变量中函数的参数改为引用传递时,如果原参数改变,对 defer 后函数的执行结果也会有影响。
func main() { x := 6 xPtr := &x zulu := func(x *int) { fmt.Println(*x) fmt.Println("Go") } defer zulu(xPtr) x = 16 } 复制代码
执行上述代码,输出结果如下:
16 Go 复制代码
与 finally 做对比
func main() { fmt.Println(f1()) # 10 } func f1() int { x := 10 defer func(){ x++ }() return x } 复制代码
返回的值并未收到影响。
将函数反射值设置为引用类型
func main() { fmt.Println(*f1()) } func f1() *int { x := 10 xPtr := &x defer func(){ *xPtr++ }() tmp := xPtr return tmp } 复制代码
再次执行,输出结果如下:
11 复制代码
输出结果受到影响。
函数 return 前会重新定义一个变量并赋值,返回的是新定义的变量,如果是值类型赋值就重新拷贝一个,对前面定义的变量没有影响,如果使用引用数据类型,这两个变量执行的是同一个内存地址,数据改变了都会跟着改变。
