• Go基础17-明确哪些函数可以作为deferred函数?


    绝大多数Gopher喜欢使用defer,因为它让函数变得简洁且健壮。但“工欲善其事,必先利其器”,要想用defer,就需要提前了解几个关于defer的关键问题,以避免掉进一些不必要的“坑”。

    1. 明确哪些函数可以作为deferred函数
      对于自定义的函数或方法,defer可以给予无条件的支持,但是对于有返回值的自定义
      函数或方法,返回值会在deferred函数被调度执行的时候被自动丢弃。
      Go语言中除了有自定义的函数或方法,还有内置函数。下面是Go语言内置函数的完整
      列表:
      append cap close complex copy delete imag len
      make new panic print println real recover
      内置函数是否都能作为deferred函数呢?我们看看下面的示例:
      // chapter4/sources/deferred_func_6.go
    func bar() (int, int) {
    return 1, 2
    }
    func foo() {
    var c chan int
    var sl []int
    var m = make(map[string]int, 10)
    m["item1"] = 1
    m["item2"] = 2
    var a = complex(1.0, -1.4)
    var sl1 []int
    defer bar()
    defer append(sl, 11)
    defer cap(sl)
    defer close(c)
    defer complex(2, -2)
    defer copy(sl1, sl)
    defer delete(m, "item2")
    defer imag(a)
    defer len(sl)
    defer make([]int, 10)
    defer new(*int)
    defer panic(1)
    defer print("hello, defer\n")
    defer println("hello, defer")
    defer real(a)
    defer recover()
    }
    func main() {
    foo()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    运行该示例:

    ./deferred_func_6.go:22:2: defer discards result of append(sl, 11)
    ./deferred_func_6.go:23:2: defer discards result of cap(sl)
    ./deferred_func_6.go:25:2: defer discards result of complex(2, -2)
    ./deferred_func_6.go:28:2: defer discards result of imag(a)
    ./deferred_func_6.go:29:2: defer discards result of len(sl)
    ./deferred_func_6.go:30:2: defer discards result of make([]int, 10)
    ./deferred_func_6.go:31:2: defer discards result of new(*int)
    ./deferred_func_6.go:35:2: defer discards result of real(a)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Go编译器给出了一组错误提示!从中我们看到,append、cap、len、make、new等内置函数是不可以直接作为deferred函数的,而close、copy、delete、print、recover等可以。

    对于那些不能直接作为deferred函数的内置函数,我们可以使用一个包裹它的匿名函数来间接满足要求。以append为例:

    defer func() {
    _ = append(sl, 11)
    }()
    
    • 1
    • 2
    • 3

    但这么做有什么实际意义需要开发者自己把握。

    2. 把握好defer关键字后表达式的求值时机

    牢记一点,defer关键字后面的表达式是在将deferred函数注册到deferred函数栈的时候进行求值的。

    下面用一个典型的例子来说明defer关键字后表达式的求值时机:

    func foo1() {
    for i := 0; i <= 3; i++ {
    	defer fmt.Println(i)
    }
    }
    func foo2() {
    	for i := 0; i <= 3; i++ {
    		defer func(n int) {
    		fmt.Println(n)
    	}(i)
    }
    }
    
    func foo3() {
    for i := 0; i <= 3; i++ {
    defer func() {
    fmt.Println(i)
    }()
    }
    }
    func main() {
    	fmt.Println("foo1 result:")
    	foo1()
    	fmt.Println("\nfoo2 result:")
    	foo2()
    	fmt.Println("\nfoo3 result:")
    	foo3()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    我们逐一分析foo1、foo2和foo3中defer关键字后的表达式的求值时机:
    在foo1中,defer后面直接接的是fmt.Println函数,每当defer将fmt.Println注册到deferred函数栈的时候,都会对Println后面的参数进行求值。根据上述代码逻辑,依次压入deferred函数栈的函数是:

    fmt.Println(0)
    fmt.Println(1)
    fmt.Println(2)
    fmt.Println(3)
    
    • 1
    • 2
    • 3
    • 4

    因此,在foo1返回后,deferred函数被调度执行时,上述压入栈的deferred函数将以
    LIFO次序出栈执行,因此输出的结果为:

    3
    2
    1
    0
    
    • 1
    • 2
    • 3
    • 4

    在foo2中,defer后面接的是一个带有一个参数的匿名函数。每当defer将匿名函数注
    册到deferred函数栈的时候,都会对该匿名函数的参数进行求值。根据上述代码逻辑,依
    次压入deferred函数栈的函数是:

    func(0)
    func(1)
    func(2)
    func(3)
    
    • 1
    • 2
    • 3
    • 4

    因此,在foo2返回后,deferred函数被调度执行时,上述压入栈的deferred函数将以
    LIFO次序出栈执行,因此输出的结果为:

    3
    2
    1
    0
    
    • 1
    • 2
    • 3
    • 4

    在foo3中,defer后面接的是一个不带参数的匿名函数。根据上述代码逻辑,依次压入
    deferred函数栈的函数是:

    func()
    func()
    func()
    func()
    
    • 1
    • 2
    • 3
    • 4

    因此,在foo3返回后,deferred函数被调度执行时,上述压入栈的deferred函数将以
    LIFO次序出栈执行。匿名函数以闭包的方式访问外围函数的变量i,并通过Println输出i的
    值,此时i的值为4,因此foo3的输出结果为:

    4
    4
    4
    4
    
    • 1
    • 2
    • 3
    • 4

    鉴于defer表达式求值时机十分重要,我们再来看一个例子:

    func foo1() {
    	sl := []int{1, 2, 3}
    	defer func(a []int) {
    	fmt.Println(a)
    	}(sl)
    	sl = []int{3, 2, 1}
    	_ = sl
    }
    func foo2() {
    	sl := []int{1, 2, 3}
    	defer func(p *[]int) {
    	fmt.Println(*p)
    	}(&sl)
    	sl = []int{3, 2, 1}
    	_ = sl
    }
    func main() {
    	foo1()
    	foo2()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    我们分别分析一下这个示例中的foo1、foo2函数。
    在foo1中,defer后面的匿名函数接收一个切片类型参数,当defer将该匿名函数注册到deferred函数栈的时候,会对它的参数进行求值,此时传入的变量sl的值为[]int{1, 2,3},因此压入deferred函数栈的函数是:

    func([]int{1,2,3})
    
    • 1

    之后虽然sl被重新赋值,但是在foo1返回后,deferred函数被调度执行时,deferred函数的参数值依然为[]int{1, 2, 3},因此foo1输出的结果为[1 2 3]。

    在foo2中,defer后面的匿名函数接收一个切片指针类型参数,当defer将该匿名函数注册到deferred函数栈的时候,会对它的参数进行求值,此时传入的参数为变量sl的地址,因此压入deferred函数栈的函数是:
    func(&sl)之后虽然sl被重新赋值,但是在foo2返回后,deferred函数被调度执行时,deferred函数的参数值依然为sl的地址,而此时sl的值已经变为[]int{3, 2, 1},因此foo2输出的结果为[3 2 1]。

  • 相关阅读:
    Android Studio编写xml布局不提示控件的部分属性问题的解决
    libevent库
    axure到底好不好学,有哪些技巧
    2022.9.17-----leetcode.1624
    淘宝/天猫获取购买到的商品订单物流信息 API分享
    stable diffusion如何解决gradio外链无法开启的问题
    `算法知识` 欧拉定理, 费马小定理
    【无标题】
    Docker01:概述与历史
    差分时钟与DDR3
  • 原文地址:https://blog.csdn.net/hai411741962/article/details/132808748