Skip to main content

Go func defer

alt text

defer + 异常

你说的对,我之前的解释有误。我们来重新分析这个代码。

package main

func test(x int) {
defer println("a")
defer println("b")

defer func() {
println(100 / x) // 这里会引发除以零异常
}()

defer println("c")
}

func main() {
test(0)
}

defer 的执行顺序

在 Go 语言中,defer 语句会将其后的函数推迟到包含该 defer 语句的函数返回之后再执行。多个 defer 语句会按照后进先出(LIFO,Last In First Out)的顺序执行。

test 函数中,defer 语句的执行顺序如下:

  1. defer println("a")
  2. defer println("b")
  3. defer func() { println(100 / x) }()
  4. defer println("c")

由于 defer 语句按照 LIFO 顺序执行,所以它们的实际执行顺序是:

  1. defer println("c")
  2. defer func() { println(100 / x) }()
  3. defer println("b")
  4. defer println("a")

异常处理

x0 时,100 / x 会引发一个除以零的运行时异常(panic)。这个异常会中断当前的执行流程,并开始逐步展开(unwind)调用栈,执行所有的 defer 语句。

实际执行顺序和输出

  1. defer println("c") 被推迟。
  2. defer func() { println(100 / x) }() 被推迟。
  3. defer println("b") 被推迟。
  4. defer println("a") 被推迟。

test(0) 执行到 return 时,开始执行 defer 语句:

  1. println("c") 输出 c
  2. println(100 / x) 尝试执行,但由于 x0,引发异常,程序崩溃。此时,其他的 defer 语句仍然会被执行。

所以,程序的实际输出是:

c
b
a
panic: runtime error: integer divide by zero

总结

在你的代码中,defer 语句按照后进先出的顺序执行。由于 100 / xx0 时会引发异常,程序在输出 cba 之后崩溃。

如果你希望捕获和处理异常,可以使用 recover 函数。以下是一个使用 recover 处理异常的示例:

package main

func test(x int) {
defer println("a")
defer println("b")

defer func() {
if r := recover(); r != nil {
println("Recovered from panic:", r)
}
}()

defer func() {
println(100 / x) // 这里会引发除以零异常
}()

defer println("c")
}

func main() {
test(0)
}

在这个示例中,recover 函数捕获了异常,程序不会崩溃,输出结果为:

c
Recovered from panic: runtime error: integer divide by zero
b
a

defer 注意事项

在 Go 语言中,defer 语句是一个非常有用的工具,可以确保某些操作在函数返回或异常发生时执行。然而,使用 defer 语句时需要注意一些事项,以避免潜在的问题。以下是一些延迟调用(defer)的使用注意事项:

1. 性能问题

defer 语句会在每次调用时创建一个函数调用记录,这在高性能要求的场景中可能会带来性能损耗。特别是在一个循环中频繁使用 defer 时,性能影响会更加明显。

建议:在性能敏感的代码中,尽量避免在循环中频繁使用 defer

// 性能敏感的场景中避免在循环中使用 defer
for i := 0; i < 1000000; i++ {
// defer 可能会带来性能开销
defer func() { println("deferred") }()
}

2. 资源管理

defer 语句常用于资源管理,如文件、网络连接和数据库连接的关闭操作。确保 defer 语句在合适的地方使用,以避免资源泄漏。

示例

file, err := os.Open("example.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close() // 确保文件在函数结束时关闭

3. 多个 defer 的执行顺序

多个 defer 语句会按照后进先出(LIFO)的顺序执行。确保理解这一点,以避免意外的行为。

示例

func example() {
defer println("first")
defer println("second")
defer println("third")
}
// 输出顺序:
// third
// second
// first

4. 捕获和处理异常

defer 语句可以与 recover 函数结合使用,以捕获和处理异常(panic)。这在需要确保程序健壮性时非常有用。

示例

func safeDivide(a, b int) {
defer func() {
if r := recover(); r != nil {
println("Recovered from panic:", r)
}
}()
println(a / b) // 可能引发除以零异常
}

func main() {
safeDivide(4, 2) // 输出 2
safeDivide(4, 0) // 捕获除以零异常
}

5. 闭包中的变量捕获

defer 中使用闭包时,注意闭包捕获的变量。闭包会捕获变量的引用,而不是值,这可能导致意外的行为。

示例

func example() {
for i := 0; i < 3; i++ {
defer func() {
println(i) // 输出 3, 3, 3 而不是 2, 1, 0
}()
}
}

修正

func example() {
for i := 0; i < 3; i++ {
defer func(i int) {
println(i) // 输出 2, 1, 0
}(i)
}
}

6. 与命名返回值的交互

在使用命名返回值时,defer 语句可以修改返回值。理解这一点对于编写正确的代码非常重要。

示例

func example() (result int) {
defer func() {
result++ // 修改命名返回值
}()
return 1 // 实际返回 2
}

7. 确保 defer 的执行

在某些情况下,defer 语句可能不会被执行。例如,os.Exit 会立即终止程序,导致 defer 语句无法执行。

示例

func example() {
defer println("This will not be printed")
os.Exit(1) // 立即终止程序
}

总之,defer 是一个强大的工具,但在使用时需要小心,确保理解其行为和潜在的陷阱。通过遵循上述注意事项,可以更好地利用 defer 语句,编写健壮和高效的 Go 代码。

defer + return

alt text

defer + nil

alt text

defer 定义时

在你的示例代码中,defer 语句与闭包(closure)的使用体现在 defer 调用中的匿名函数。闭包是指那些能够捕获其外部环境变量的函数。在 Go 语言中,匿名函数通常被用作闭包。

以下是你的代码示例的详细解释和输出结果:

package main

import (
"errors"
"fmt"
)

func foo(a, b int) (i int, err error) {
defer fmt.Printf("first defer err %v\n", err)
defer func(err error) { fmt.Printf("second defer err %v\n", err) }(err)
defer func() { fmt.Printf("third defer err %v\n", err) }()
if b == 0 {
err = errors.New("divided by zero!")
return
}

i = a / b
return
}

func main() {
foo(2, 0)
}

foo 函数中,有三个 defer 语句:

  1. 第一个 defer

    defer fmt.Printf("first defer err %v\n", err)

    这个 defer 语句直接调用 fmt.Printf,它会在 foo 函数返回时执行,打印当前的 err 值。

  2. 第二个 defer

    defer func(err error) { fmt.Printf("second defer err %v\n", err) }(err)

    这个 defer 语句使用了一个闭包,并将当前的 err 值作为参数传递给闭包。由于参数是按值传递的,所以这个闭包中的 err 值在 defer 语句定义时就已经确定了。

  3. 第三个 defer

    defer func() { fmt.Printf("third defer err %v\n", err) }()

    这个 defer 语句也使用了一个闭包,但它捕获了外部变量 err。因此,这个闭包中的 err 值在 foo 函数返回时才会被确定。

foo(2, 0) 被调用时,由于 b == 0err 被赋值为 "divided by zero!",然后函数返回,触发所有 defer 语句的执行。执行顺序是 LIFO(后进先出)的。

输出结果如下:

third defer err divided by zero!
second defer err <nil>
first defer err divided by zero!

解释:

  1. 第三个 defer:捕获了外部变量 err,在函数返回时打印 "divided by zero!"
  2. 第二个 defer按值传递了 err,在 defer 语句定义时 err 还是 nil 这个不是一个函数,所以不是运行时的
  3. 第一个 defer:直接调用 fmt.Printf,在函数返回时打印 "divided by zero!"

这个示例展示了 defer 语句与闭包结合使用的效果,特别是闭包如何捕获和使用外部变量。