Golang 中的 defer


2022年1月15日, Learn eTutorial
2004

在本教程中,您将学习 Go 编程语言中的 defer 语句。从上一个教程中,您学习了像 ifswitchfor 循环for –range 循环这样的 控制流语句,这些语句在其他编程语言中非常常见。但 Golang 中的 defer 并不像其他控制流语句那样常见。

Golang 中的 defer 是什么?

defer 这个词的意思是“推迟(一项行动或事件)到稍后或推迟”。在 Go 编程语言中,带有 defer 关键字的语句指定了一个语句需要被推迟或稍后执行,但应该在退出主循环之前执行。

  •   defer 关键字用于将语句指定为 defer。
  •   通常用于 Go 程序中的清理函数。
  •   使用 defer 关键字推迟执行语句,直到调用函数结束。
  •   在函数退出前返回值。
  •   这里 defer 语句可以是一个函数或一个表达式。

它被实现为一个栈数据结构,除了 defer 关键字函数调用之外,所有函数都以后进先出(LIFO)的方式在栈上调用。它仅在最后一个函数调用返回后才被调用。

defer 在 Golang 中如何工作?

在正常的 Go 应用程序或程序中,控制流从我们调用的任何函数的顶部流向底部。通常,一个函数从第一行开始执行,直到最后一行。
让我们通过一个程序来理解这一点。当我们运行下面的代码时,它会按顺序运行并输出“first”、“second”、“third”。


package main
import "fmt"
func main() {
    
    fmt.Println("First")
    fmt.Println("Second")
    fmt.Println("Third")
   
}

输出


First
Second
Third

defer 关键字用于推迟语句的执行。假设将 defer 关键字放在 fmt.Println("Second") 前面,您会注意到 second 将在 third 之后打印。


package main
import "fmt"
func main() {
    
    fmt.Println("First")
    defer fmt.Println("Second")   //defer keyword
    fmt.Println("Third")
    
    
}

输出


First
Second
Third

defer 关键字在函数完成最后一条语句后,但在函数实际返回之前执行传递给它的任何函数。所以,main 函数 () 的执行方式是:它打印上面示例中的第一条语句的输出“first”。然后,在下一条语句中识别函数调用中的 defer 关键字,然后打印最后一条语句“third”。最后,main 函数 () 退出并检查是否有任何已 defer 的函数需要调用。由于有一个 defer 函数,所以继续调用该函数。

注意: defer 实际上并不会移动到 main 函数的末尾,而是将其移动或在 main 函数执行之后、但在 main 函数返回之前工作。
  •   Defer 关键字按后进先出顺序或反向顺序工作。
  •   当编译器遇到函数中的 defer 语句时,它会将其推入栈数据结构。
  •   所有遇到的 defer 语句都被推入栈中。
  •   当包围函数返回时,栈中的所有函数从上到下执行,然后才能在调用函数中开始执行。调用函数也发生同样的事情。
     

使用 Defer 语句进行资源清理

Golang 编程语言中 defer 关键字的关键优势在于资源清理,例如打开文件、处理数据库、网络连接等。在程序执行期间使用某些资源成功完成后,需要关闭每个使用的资源,以避免当其他程序等待同一资源时发生资源冲突。

Defer 语句通过关闭程序执行期间打开的所有文件或资源,使 Go 代码无错误。

在 Go 中,defer 通常用于资源清理。让我们通过一个写入文件的示例来理解。打开文件后需要关闭它。
 

写入文件的程序


package main
import (
    "fmt"
    "log"
    "os"
)

func main() {
    err := writeToFile("Some text")
    if err != nil {
        log.Fatalf(err.Error())
    }
    fmt.Printf("Write to file succesful")
}

func writeToFile(text string) error {
    file, err := os.Open("temp.txt")
    if err != nil {
        return err
    }
    n, err := file.WriteString("Some text")
    if err != nil {
        return err
    }
    fmt.Printf("Number of bytes written: %d", n)
    file.Close()
    return nil
}

上面给出的 Go 代码打开一个文件。writeToFile 函数打开一个文件并向文件写入一些信息/内容。在完成写入文件后,下一步是关闭文件。写入操作可能会发生错误。在这种情况下,它将返回一个错误并退出函数。函数尝试关闭文件,使用的资源将被释放回系统。最后,在成功完成写入文件后返回一个 nil 值。

如果 file.WriteString 函数调用失败,函数将在不关闭文件的情况下返回,并将资源释放回系统。使用 defer 关键字而不是 file.Close() 语句可以修复此问题。defer 关键字在函数返回之前执行,以避免或修复此类问题。

重写上述代码后如下所示


package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    err := writeToFile("Some text")
    if err != nil {
        log.Fatalf(err.Error())
    }
    fmt.Printf("Write to file succesful")
}

func writeToFile(text string) error {
    file, err := os.Open("temp.txt")
    if err != nil {
        return err
    }
    defer file.Close()    //defer statement

    n, err := file.WriteString("Some text")
    if err != nil {
        return err
    }
    fmt.Printf("Number of bytes written: %d", n)
    return nil
}

defer file.Close() 在打开文件后关闭文件。这会推迟 file.close() 的执行,确保即使在写入同一文件时发生错误,文件也能被关闭。

Golang 中的多个 defer 关键字

一个 Golang 程序可以在程序中使用多个 defer 关键字。让我们通过下面的示例来检查,其中执行了三个 defer 语句。栈的概念在这里起作用。

  •   第一个 defer 语句打印 first。(顶层-2)
  •   第二个 defer 语句打印 second。(顶层-1)
  •   第三个 defer 语句打印 third。(顶层)
     

这些被推入栈中。顶层持有最后打印的语句,即 third。

GO : Defer

观察下面代码的输出,它按照推入栈的顺序反向打印。被 defer 的语句一个接一个地排列,导致 后进先出 的方式。

带有多个 defer 关键字的程序


package main
import "fmt"

func main() {
    defer fmt.Println("First")
    defer fmt.Println("Second")
    defer fmt.Println("Third")
}

输出


First
Second
Third

如何评估 defer 参数?

Defer 参数在 defer 语句被求值时进行求值。
让我们通过一个简单的程序来理解
 


package main
import "fmt"

func main() {
 statement := "Go language in Learn  eTurorials"

 defer fmt.Printf("In defer statement  is: %s\n", statement)
 statement = "Learn  eTurorials"

  }

输出


In defer statement is: Go language in Learn  eTurorials

在给定的程序中,一个字符串数据类型的变量 statement 被视为 defer 语句。defer 语句通过赋给 statement 变量的值 Go language in Learn eTutorials 进行求值。defer 语句打印变量 statement 的值。稍后在下一行更改 statement 变量的值为 “Learn eTutorials”。您可以看到上面代码的输出,它只评估 defer 语句变量,尽管赋给同一变量的值已经发生了变化。