Go 编程 | 连载 25 - Go 的 defer 语句
一、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
前会重新定义一个变量并赋值,返回的是新定义的变量,如果是值类型赋值就重新拷贝一个,对前面定义的变量没有影响,如果使用引用数据类型,这两个变量执行的是同一个内存地址,数据改变了都会跟着改变。
转载自:https://juejin.cn/post/7134165048653512718