基本概念

Go言语的推迟句子defer有哪些特色?通常在什么情况下运用?

Go言语的推迟句子(defer statement)具有以下特色:

  1. 推迟履行:推迟句子会在包括它的函数履行结束前履行,无论函数是正常回来还是产生异常。

  2. 后进先出:如果有多个推迟句子,它们会依照后进先出(LIFO)的顺序履行。也就是说,最终一个推迟句子会最早履行,而第一个推迟句子会最终履行。

通常情况下,推迟句子在以下情况下运用:

  1. 资源开释:推迟句子能够用于在函数回来前开释打开的文件、封闭数据库衔接、开释锁等资源,以确保资源的正确开释,防止资源泄漏。

  2. 过错处理:推迟句子能够用于处理函数履行过程中可能产生的过错。经过在函数开始时设置推迟句子,在函数回来前检查过错并进行相应的处理,能够简化过错处理的逻辑。

  3. 日志记录:推迟句子能够用于在函数回来前记录日志或履行其他的调试操作,以便在函数履行过程中收集相关的信息。

推迟句子的运用能够进步代码的可读性和可维护性,一起确保资源的开释和整理操作依照逆序进行。它是Go言语中一种常用的编程技巧,用于处理资源管理和过错处理等场景。

避坑之旅

实践开发中defer的运用并不像前面介绍的这么简单,defer用欠好,会堕入泥潭。

下面我从两个角度带咱们避坑:

  1. 首要拆解一下推迟句子的履行,留意Go言语的return句子不是原子性的;

  2. 别的重点和咱们分享一下defer句子后边运用匿名函数和非匿名函数的差异。

拆解推迟句子

防止堕入泥潭的关键是必须深刻理解下面这条句子:

return xxx

上面这条句子经过编译之后,实践上生成了三条指令:

1)回来值 =xxx。

2)调用 defer 函数。

3)空的 return。

第1和第 3 步是return句子生成的指令,也就是说return并不是一条原子指令;

第2步是 defer 界说的句子,这儿可能会操作回来值,然后影响最终成果。

下面来看两个比如,试着将return 句子和 defer句子拆解到正确的顺序。

第一个比如:

func f()(r int){
  t:=5
  defer func(){
    t=t+5
    }()
  return t
}

拆解后:

func f()(r int){
  t:=5
  //1,赋值指令
  r=t
  // 2.defer 被插入到赋值与回来之间履行,这个比如中回来值r没被修改正 
  func(){
    t=t+5
    }()
  //3.空的 return 指令
  return
  }

这儿第二步实践上并没有操作回来值r,因而,main函数中调用f()得到5。

防止defer圈套:拆解推迟句子,把握正确运用方法

第二个比如:

func f()(r int){
  defer func(r int){
    r=r+5
    }(r)
    return 1
}

拆解后:

func f() (r int) {
  //1.赋值 
  r=1
  //2.这儿改的r是之前传进去的r,不会改动要回来的那个r值 
  func(r int) {
    r=r+5
  }(r)
  // 3. 空的 return 
  return
}

第二步,改动的是传值进去的r,是形参的一个仿制值,不会影响实参r。因而,main函数中需求调用f()得到1。

防止defer圈套:拆解推迟句子,把握正确运用方法

defer匿名函数

在Go言语中,运用匿名函数作为defer的参数时,能够理解为:defer句子中的匿名函数在包裹该defer句子的函数回来后才履行。这是由于defer句子的履行机遇是在包裹函数行将回来之前,但在实践回来之前。

为什么不是在return句子之前履行呢?这是由于defer句子的规划初衷是为了在函数回来之前履行一些整理操作,例如封闭文件、开释资源等。将defer句子放在return句子之后,能够确保在函数回来之前履行这些整理操作,确保函数的履行完整性和资源的正确开释。

在运用匿名函数和非匿名函数作为defer的参数时,首要差异在于对函数参数的传递和效果域的影响:

  1. 匿名函数作为defer的参数:匿名函数能够直接在defer句子中界说,能够拜访外部函数的变量,并且在履行时会运用当时的变量值。这种方法能够方便地在defer句子中运用外部变量,但需求留意变量的值在履行时可能已经产生了改动。

  2. 非匿名函数作为defer的参数:非匿名函数需求先界说好,然后作为defer的参数传递。在履行时,会运用函数的当时参数值。这种方法能够在defer句子中运用已界说的函数,但需求留意函数参数的传递和效果域。

产生这种差异的原因是,匿名函数和非匿名函数在界说和效果域上的差异。匿名函数能够直接在defer句子中界说,能够拜访外部函数的变量,而非匿名函数需求先界说好,然后作为参数传递。这种规划灵活性使得开发者能够根据详细的需求挑选合适的方法来运用defer句子。

举例来说

当运用匿名函数作为defer的参数时,能够在defer句子中直接界说匿名函数,并拜访外部变量。

以下是一个示例代码:

package main
import "fmt"
func main() {
    x := 10
    defer func() {
        fmt.Println("Deferred anonymous function:", x)
    }()
    x = 20
    fmt.Println("Before return:", x)
}

在上述示例中,匿名函数作为defer的参数,能够拜访外部变量x。 在函数回来之前,defer句子中的匿名函数会履行,并打印出x的值。

输出成果如下:

防止defer圈套:拆解推迟句子,把握正确运用方法

当运用非匿名函数作为defer的参数时,需求先界说好函数,然后将函数名作为defer的参数传递。

以下是一个示例代码:

package main
import "fmt"
func main() {
    x := 10
    defer printX(x)
    x = 20
    fmt.Println("Before return:", x)
}
func printX(x int) {
    fmt.Println("Deferred function:", x)
}

在上述示例中,printX函数作为defer的参数传递,函数界说在main函数之后。

在函数回来之前,defer句子中的printX函数会履行,并打印出传递的参数x的值。输出成果如下:

防止defer圈套:拆解推迟句子,把握正确运用方法

总结一下

经过以上示例,咱们能够清晰体现出运用匿名函数和非匿名函数作为defer的参数的差异。

匿名函数能够直接在defer句子中界说,并拜访外部变量,而非匿名函数需求先界说好函数,然后将函数名作为参数传递。

经过前面带着咱们拆解了defer的句子的履行,信任咱们能够更好的理解了。

更多defer运用的技巧和踩坑经历,欢迎在谈论区交流讨论。