Defer, Panic, and Recover(一) --- defer

80 阅读2分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。

Go 具有常见的一些控制流机制:if、for、switch、goto。 它还具有在单独的 goroutine 中运行代码的 go 语句。 在这里,我想讨论一些不太常见的问题:defer, panic, recover.

defer 语句将函数调用推送到一个列表中。保存的调用列表函数会在外围函数返回后执行。 Defer 通常用于简化执行各种清理操作的函数。

例如,让我们看一个打开两个文件并将一个文件的内容复制到另一个文件的函数:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}

这种写法可以运行,但有一个错误。 如果调用 os.Create 失败,函数将返回而不关闭源文件。 这可以通过在第二个 return 语句之前调用 src.Close 来轻松解决,但如果函数更复杂,问题可能不会那么容易被注意到和解决。 通过引入 defer 语句,我们可以确保文件始终关闭:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

Defer 语句允许我们考虑在打开每个文件后立即关闭它,保证无论函数中的 return 语句数量如何,文件都将被关闭。

defer 语句的行为是直接且可预测的。 有三个简单的规则:

  • defer 语句的参数在defer语句被衡量的时候,才会被衡量。

在下面例子中,当 Println 调用被延迟时,表达式“i”被计算是在Println函数被真正执行的时候。 函数返回后,延迟调用将打印“0”。

    i := 0
    defer fmt.Println(i)
    i++
    return
}

延迟函数会在外部函数返回后按后进先出顺序执行。

这个函数会打印“3210”

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}

延迟函数可以读取并分配给函数已经指定的返回值。

func c() (i int) {
    defer func() { i++ }()
    return 1
}

这样方便修改函数的错误返回值; 我们很快就会看到一个例子。