文章目录
- 1 defer 定义
- 1.1 简单示例
- 1.2 具体示例
- 2 defer 常用场景
- 3 defer 规则
- 3.1 当申明defer 时,参数就已经解析了
- 3.2 defer执行顺序为先进后出
- 3.3 defer可以读取有名返回值
1 defer 定义
defer
英文原意: vi. 推迟;延期;服从 vt. 使推迟;使延期。
defer
的思想类似于C++中的析构函数,不过Go语言中“析构”的不是对象,而是函数,defer就是用来添加函数结束时执行的语句。
析构函数(destructor) 与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。 析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。
defer
代码块会在函数调用链表中增加一个函数调用。这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用。因此,defer通常用来释放函数内部变量。
1.1 简单示例
- 首先我们现用简单的例子来体验一下defer函数
package main
import (
"fmt"
)
func main() {
fmt.Println("Step 1")
fmt.Println("Step 2")
defer fmt.Println("Step Final")
fmt.Println("Step 3")
fmt.Println("Step 4")
}
输出
Step 1
Step 2
Step 3
Step 4
Step Final
我们将defer ("Step Final")
换行, 其输出结果也如上所示
func main() {
defer fmt.Println("Step Final")
fmt.Println("Step 1")
fmt.Println("Step 2")
fmt.Println("Step 3")
fmt.Println("Step 4")
}
输出
Step 1
Step 2
Step 3
Step 4
Step Final
- 非主函数:
func f() (result int) {
defer func() {
result++
}()
return 0
}
上面 返回结果是 1,因为defer中添加了一个函数,在函数返回前改变了命名返回值的值。是不是很好用呢。但是,要注意的是,如果我们的defer语句没有执行,那么defer的函数就不会添加,如果把上面的程序改成这样:
func f() (result int) {
return 0
defer func() {
result++
}()
return 0
}
上面的函数就返回0了,因为还没来得及添加defer的东西,函数就返回了。
1.2 具体示例
假设我们想要创建一个文件,写入它,然后在我们完成时关闭1。
在文件操作的时候,均需要进行关闭文件操作, 所以我们来用 defer 完成关闭文件操作。
package main
import "fmt"
import "os"
func main() {
f := createFile("/tmp/")
defer closeFile(f)
writeFile(f)
}
func createFile(p string) *os.File {
fmt.Println("creating")
f, err := os.Create(p)
if err != nil {
panic(err)
}
return f
}
输出:
creating
writing
closing
2 defer 常用场景
通过defer,我们可以在代码中优雅的关闭/清理代码中所使用的变量。defer作为golang清理变量的特性,有其独有且明确的行为。
defer
经常和 panic
以及 recover
一起使用,判断是否有异常,进行收尾操作。
package main
import "fmt"
func main(){
defer func(){ // 必须要先声明defer,否则不能捕获到panic异常
fmt.Println("c")
if err:=recover();err!=nil{
fmt.Println(err) // 这里的err其实就是panic传入的内容,55
}
fmt.Println("d")
}()
f()
}
func f(){
fmt.Println("a")
panic(55)
fmt.Println("b")
fmt.Println("f")
}
输出结果:
a
c
55
d
3 defer 规则
3.1 当申明defer 时,参数就已经解析了
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
输出
0
上面我们说过,defer函数会在return之后被调用。那么这段函数执行完之后,是不用应该输出1呢?
读者自行编译看一下,结果输出的是0. why?
这是因为虽然我们在defer后面定义的是一个带变量的函数: (i). 但这个变量(i)在defer被声明的时候,就已经确定其确定的值了2。
3.2 defer执行顺序为先进后出
当同时定义了多个defer代码块时,golang安装先定义后执行的顺序依次调用defer。
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}
输出:
3
2
1
0
3.3 defer可以读取有名返回值
func c() (i int) {
defer func() { i++ }()
return 1
}
输出结果是 2. 在开头的时候,我们说过defer是在return调用之后才执行的。 这里需要明确的是defer代码块的作用域仍然在函数之内,结合上面的函数也就是说,defer的作用域仍然在c函数之内。因此defer仍然可以读取c函数内的变量
-
/defer ↩︎
-
/articles/10167 ↩︎