defer 基础知识回顾
defer 作用:
defer是一种延迟执行机制,它确保在函数即将退出前执行特定的操作,如释放资源(如关闭数据库连接或文件流)。执行顺序:
defer遵循栈的后进先出(LIFO)原则,即先定义的defer语句会在函数退出时最后执行,后定义的defer语句则最先执行。与 return 的关系:尽管
defer在函数返回之前执行,但return本身分为两步:首先计算返回值,然后执行defer语句,最后完成函数的返回。
defer 相关知识点总结
1. defer 执行顺序
defer的执行顺序严格按照它们在代码中定义的逆序进行。
2. defer 与 return 的先后顺序
通过实例代码演示,我们发现尽管defer总是在return之前执行,但在return语句开始执行前,会先完成返回值的赋值操作。
3. 函数返回值初始化与 defer 的间接影响
在函数返回前,defer可以修改返回值,最终函数实际返回的是经过defer修改后的值。
4. defer 遇见 panic
当函数中发生panic时,先前定义的所有defer仍按顺序执行。
5. defer 中包含 panic
即使defer内的匿名函数触发了panic,defer依然会按顺序执行,并且defer能够捕获到panic的信息。
6. defer 下的函数参数包含子函数
在defer表达式中调用带有子函数的参数时,会先执行子函数以获取其结果值,然后按照defer的栈顺序依次执行。
示例解析
示例一:defer 和 return 结合使用
package main
import "fmt"
func main() {
function1()
}
func function1() {
fmt.Printf("1")
defer function2()
fmt.Printf("2")
}
func function2() {
fmt.Printf("3")
}
// 输出结果:
// 1 2 3
示例二:defer 修改返回值
func demo3() (a int) {
defer func() {
a = 3
}()
return 1
}
// 输出结果:
// 3
示例三:局部变量值拷贝对defer的影响
func demo7() int {
var a int
defer func(a int) {
a = 10
}(a)
return 2
}
// 输出结果:
// 10 2
这里解释了为何demo7函数返回10而非2:由于在执行defer时传入的是a的副本,而不是引用,所以修改副本并不会影响原始变量a的值。如果传递的是指向a的指针,那么结果将会不同。
示例四:defer 遇到 panic
func demo4() {
defer func() {
fmt.Println("1")
}()
defer func() {
fmt.Println("2")
}()
panic("panic")
defer func() {
fmt.Println("3")
}()
defer func() {
fmt.Println("4")
}()
}
// 输出结果:
// 2 1 panic: panic
// goroutine 1 [running]:
// main.demo4()
// 分析:
// 即使发生了panic,仍然执行了部分defer语句。由于panic导致程序中断,后续的defer未被执行。
示例五:defer 中使用 recover 处理 panic
func demo5() {
defer func() {
fmt.Println("1")
if err := recover(); err != nil {
fmt.Println(err)
}
}()
defer func() { fmt.Println("2") }()
panic("panic")
// 不会被执行的defer语句...
}
// 输出结果:
// 2 1 panic
总结来说,defer的使用虽简单,但仍需注意以下几点:
defer在函数返回前执行,其执行顺序优于return,且return包含两步操作:先确定返回值,然后执行defer,最后执行return返回调用者。即使函数内部出现
panic异常,panic发生前已定义的defer仍然会被执行。若
defer内包含子函数调用,子函数会按defer定义的顺序先行执行,而整个defer逻辑则是按照栈的顺序执行。