历史上,defer的概念是在Go中引入的,以安全释放资源和在函数执行结束时进行最终处理,无论是正常结束还是由于panic结束。然而,defer与return和panic之间的交互存在一些令人恼火的陷阱,即使是经验丰富的开发者也常常忽视。
问题在于,返回值的计算顺序、命名返回值的工作,以及在defer中更改这些值的行为与许多语言的习惯行为有很大不同。此外,如果defer中尝试修改已计算的值,可能会导致意想不到的行为。
解决方案是始终记住:函数返回的值是在defer运行之前计算的,但如果使用的是命名结果,可以在defer中更改这些值,在实际返回函数之前。
示例代码:
func tricky() (res int) { defer func() { res = 42 // 更改返回值! }() return 10 } func main() { fmt.Println(tricky()) // 将输出42,而不是10 }
关键特性:
延迟(defer)函数以什么顺序执行?
它们以声明的相反顺序严格执行(栈 — LIFO)。
func f() { defer fmt.Println("1") defer fmt.Println("2") } // 输出:2,然后是1
defer函数的参数在何时计算 — 在defer声明时还是在执行时?
defer函数的参数在defer声明时计算,而不是在调用时。
func f() { i := 1 defer fmt.Println(i) // 将输出1,即使i之后改变了 i = 2 }
defer能否更改非命名的函数结果?
不能。只有命名返回值可以在defer中更改。
func f() int { defer func() { /* 不能更改 */ }() return 5 }
一个年轻开发者想在defer中记录返回代码,结果错误地更改了命名返回值,从而“覆盖”了函数的实际结果。
优点:
缺点:
在另一种情况下,defer仅用于释放资源、记录日志,并且不更改返回值,而重要的值在返回之前显式分配。
优点:
缺点: