go

golang中defer,return的执行顺序

介绍

在Go语言中,defer语句用于预定一个函数调用(或者方法调用),这个调用会在包含它的函数执行完毕后,即将返回之前执行。如果有多个defer语句,它们的执行顺序是后进先出(LIFO),类似栈。

当一个函数执行到return语句时,任何被延迟的函数调用都会在该函数的其他代码执行完毕后、实际返回调用方之前执行。如果defer语句中包含了匿名函数,那么这个匿名函数也会遵循相同的规则。即:

  1. 计算返回值并赋值
  2. defer
  3. return

下面根据代码来解释具体执行步骤。

代码示例

示例1

package main

import "fmt"

type Foo struct {
    v int
}

// NewFoo函数接受一个整数的指针n,打印指针指向的值,并返回一个Foo类型的实例。
func NewFoo(n *int) Foo {
    fmt.Println(*n)
    return Foo{}
}

// Foo类型有一个方法Bar,它同样接受一个整数的指针n并打印指针指向的值
func (f Foo) Bar(n *int) {
    fmt.Println(*n)
}

func main() {
    x := 1
    y := &x
    defer NewFoo(y).Bar(y)
    x = 2
    y = new(int)
    NewFoo(y)
}

1
0
2

上面的代码中defer语句包括了函数的多级调用。在官方文档中 https://go.dev/ref/spec#Defer_statements
可以找到下面一段话:

Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. That is, if the surrounding function returns through an explicit return statement, deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller. If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the "defer" statement is executed.

每次执行“defer”语句时,调用的函数值和参数都会像往常一样计算并重新保存,但不会调用实际函数。相反,延迟函数在周围函数返回之前立即调用,其延迟顺序与延迟顺序相反。也就是说,如果周围的函数通过显式返回语句返回,则在该返回语句设置任何结果参数之后,但在函数返回给其调用方之前,将执行延迟函数。如果延迟函数值的计算结果为 nil ,则执行在调用函数时会崩溃,而不是在执行“defer”语句时。这是翻译过来的,说人话就是:

假设我们在函数A的函数体内运行了defer B(params),那被defer的函数B的参数会像普通函数调用一样被即时计算,但是被defer的函数B的调用会延迟到函数A return或者panic之前执行。如果defer后面跟的是多级函数的调用,只有最后一个函数会被延迟执行。

执行顺序

下面来具体看一下执行顺序是怎样的:

  • `x := 1` 定义了一个整数x并初始化为1。
  • `y := &x` 创建了一个指向x的指针y。
  • `defer NewFoo(y).Bar(y)` defer语句会立即对参数y进行求值,但是延迟执行函数本身。这意味着NewFoo(y)会立即执行,打印1(因为y指向x,x的值为1),并返回一个Foo实例。然而,Bar方法的调用会被延迟到main函数返回之前执行。
  • `x = 2` x的值被更新为2。
  • `y = new(int)` 修改指针y的值,指向了int的零值,即0。
  • `NewFoo(y)` 调用NewFoo(y),打印出y指向的值,即0。
  • `Bar(y)` 当main函数执行完毕并准备返回之前,延迟的Bar方法调用将被执行。Bar方法是在defer中被延迟调用的,它使用的是当时的y的值,即指向x的指针。但是x被更新为了2,所以这里Bar方法将打印2。

示例2

package main

import "fmt"

type T int

func (t T) M(n int) T {
    fmt.Println(n)
    return t
}

func main() {
    var t T
    defer t.M(1).M(2)
    t.M(3).M(4)
}

1
3
4
2

上面的代码中定义了一个类型T,类型T有一个方法M,该方法接受一个整数n作为参数,打印这个整数,并返回接收者自身(即类型为T的值)。

执行顺序

`t.M(3).M(4)`

  • 首先,调用t.M(3)。这会打印3,并返回t(注意,由于T是基于值的类型,这里的返回是t的一个副本)。
  • 然后,使用上一步返回的t的副本调用.M(4)。这会打印4,并再次返回t的副本(这个返回值在这个表达式中没有被进一步使用)。

`defer t.M(1).M(2)`

  • defer语句会在main函数返回之前执行,但是它会立即对其参数进行求值。这意味着t.M(1)会被立即调用,打印1,并返回t的副本。
  • 然后,这个返回的副本被用来调用.M(2)。不过,这个调用是被延迟的,它不会立即执行。它会在main函数返回之前执行。

综上,执行顺序为:

  1. t.M(1)被立即调用,打印1。
  2. t.M(3).M(4)按顺序打印3和4。
  3. 在main函数返回之前,延迟的t.M(1).M(2)中的.M(2)被执行,打印2。

示例3

package main

import "fmt"

type Add func(int, int) int

func main() {
    var f Add
    defer f(1, 2)
    fmt.Println("end")
}

上面的代码定义了一个Add类型,它是一个函数类型,接受两个int类型的参数,并返回一个int类型。main函数中声明了一个Add类型的变量f,使用defer调用f,最后打印出"end"。

这段代码会先输出end,然后会报错`panic: runtime error: invalid memory address or nil pointer dereference`。这是因为f在被defer调用时还没有被赋予一个具体的函数实现,这意味着f是nil。在Go语言中,尝试调用一个nil的函数值会导致panic,因为没有函数体可以执行。

为了让这段代码正常工作,需要在使用defer f(1, 2)之前为f赋予一个具体的函数实现。修改后的代码如下:

package main

import "fmt"

type Add func(int, int) int

func main() {
    var f Add = func(a int, b int) int {
        return a + b
    }
    defer func() {
        fmt.Println(f(1, 2))
    }()
    fmt.Println("end")
}

end
3

这里f被赋予了一个匿名函数,该函数接受两个int参数并返回它们的和。然后,使用一个匿名函数来包裹defer对f的调用。这样,当defer语句执行时,它会调用f,打印出两个数字(1和2)的和,即3。

示例4

package main

import (
    "fmt"
    "os"
)

func f1() {
    fmt.Println("func f1")
}

func main() {
    defer f1()
    os.Exit(0)
}

os.Exit(0)调用立即终止了程序,所以延迟的f1函数没有机会运行。所以这个代码没有任何输出。即使defer语句执行了,被defer的函数也不一定会执行。

代码示例5

package main

import (
    "fmt"
)

func f1() int {
    x:=5
    defer  func(){
        x++
    }()
    return x
}

func f2()(x int){
   defer func(){
        x++
   }()
   return 5
}

func f3() (y int){
    x:=5
    defer func(){
        x++
    }()
    return x
}

func f4()(x int){
   defer func (x int){
       x++
   }(x)
    return 5
}

func f5()(x int){
   defer func (x int) int {
       x++
       return x
   }(x)
   return 5
}

func f6()(x int){
   defer func (x *int) *int {
       *x++
       return x
   }(&x)
   return 5
}

func main(){
   fmt.Println(f1()) // 5
   fmt.Println(f2()) // 6
   fmt.Println(f3()) // 5
   fmt.Println(f4()) // 5
   fmt.Println(f5()) // 5
   fmt.Println(f6()) // 6
}
  • f1函数中,defer后的匿名函数修改的是x,而不是返回值,所以返回的是x初始值5。
  • f2函数中,defer后的匿名函数直接修改的是返回值x,所以返回的是5+1=6。
  • f3函数中,虽然defer后的匿名函数试图修改x,但是返回的是y,y的值在返回值赋值阶段已经被确定为5,所以返回的是5。
  • f4函数中,defer后的匿名函数修改的是传入的参数x的副本,而不是返回值x,所以返回的是5。
  • f5函数中,defer后的匿名函数修改的是传入的参数x的副本,并且返回值被忽略,所以返回的是5。(在defer语句中,我们修改了x的值,但是在return语句中,我们又给x赋了一个新的值。这时候,return语句给x赋的新值就覆盖了defer语句修改的值。)
  • f6函数中,defer后的匿名函数通过指针修改的是返回值x,所以返回的是5+1=6。(return语句会先将返回值5赋给x,然后再执行defer语句。在defer语句中,我们又给x赋了一个新的值6。因为defer语句在return 5语句之后执行,所以defer语句给x赋的新值6会覆盖return语句给x赋的值5,return x返回6.)
分类: go
0 0 投票数
文章评分
订阅评论
提醒
guest

0 评论
内联反馈
查看所有评论

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部
0
希望看到您的想法,请您发表评论x