defer特性
1.关键字defer 用于注册延迟调用
2.这些调用直到return 前才被执行。因此,可以用来做资源清理
3.多个defer语句,按先进后出的方式执行
4.defer语句中的变量,在defer生命时就决定了
defer用途
1.关闭文件句柄
2.锁资源释放
3.数据库连接释放
defer用例
1.多个defer输出顺序
func main() {for i := 0; i < 3; i++ {defer fmt.Println(i) // 1. 这三个 defer 语句会按照 i=0, i=1, i=2 的顺序被推入栈中。}fmt.Println("循环结束") // 2. 循环结束,打印 "循环结束"。if true {defer fmt.Println("条件语句中的 defer") // 3. 这个 defer 语句被推入栈中。}defer fmt.Println("最后的 defer") // 4. 这个 defer 语句被推入栈中。fmt.Println("main 函数执行中") // 5. 打印 "main 函数执行中"。
}//结果
//循环结束
//main 函数执行中
//最后的 defer
//条件语句中的 defer
//2
//1
//0
2.defer遇到循环
//第一个例子
package mainimport "fmt"type Test struct {name string
}func (t *Test) Close() {fmt.Println(t.name, " closed")
}
func main() {ts := []Test{{"a"}, {"b"}, {"c"}}for _, t := range ts {defer t.Close()}
}//结果:c closed
//c closed
//c closed
//第二个例子
package mainimport "fmt"type Test struct {name string
}func (t *Test) Close() {fmt.Println(t.name, " closed")
}
func Close(t Test) {t.Close()
}
func main() {ts := []Test{{"a"}, {"b"}, {"c"}}for _, t := range ts {defer Close(t)}
}
//结果
//c closed
//b closed
//a closed
第一个例子中,当defer语句被执行时,它都会捕获t当前值的副本,并不是t的地址。由于t是在循环遍历的,每次迭代都会创建t的副本,而不是t的地址。由于t是在循环中逐一遍历的,每次迭代都会创建t的新副本。因此当main函数结束时,defer语句会按照后进先出的顺序执行,但是每个defer调用都会打印循环结束时t的最终副本,即“c”。
第二个例子中的close函数接受一个Test类型的参数,并且调用这个参数的Close方法。由于Close是一个单独的函数,它接收t的副本作为参考。当defer调用Close(t)时,它传递了t的当前值的副本给Close函数。这意味着每个defer调用都捕获了循环中t的当前值的副本。
3.defer +recover 捕获异常
package mainimport "fmt"func safeDivide(a, b int) (int, error) {defer func() {if r := recover(); r != nil {fmt.Println("Recovered from panic:", r)}}()if b == 0 {return 0, fmt.Errorf("cannot divide by zero")}return a / b, nil
}func main() {result, err := safeDivide(10, 0)if err != nil {fmt.Println("Error:", err)}fmt.Println("Result:", result)
}
4.defer 遇到闭包
package mainimport "fmt"func main() {var whatever [5]struct{}for i := range whatever {defer func() { fmt.Println(i) }()}
}
//结果
//4
//4
//4
//4
//4
这是因为闭包用到的变量i在执行时就已经变成4了,所以输出全是4
注意事项
注意事项
-
性能:过度使用
defer
可能会影响性能,因为每个defer
调用都会创建额外的栈帧。 -
控制流:
defer
语句的执行顺序可能与直觉相反,需要仔细设计以确保逻辑正确。 -
错误处理:
defer
语句中的错误处理代码应该能够处理所有可能的错误情况。 -
嵌套使用:嵌套使用
defer
时,确保外层的defer
能够正确处理内层可能抛出的错误。