• Golang 中的 defer 关键字


    0x00 defer 是啥

    用一段简单的代码演示

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
        defer fmt.Println("this defer fmt!!")
        fmt.Println("this is normal fmt!!")
    }
    

    以上代码的输出如下:

    image-20211220115753113

    defer 的作用就是注册一个方法的调用,该方法将在程序返回后进行输出。顺带,defer 的执行队列是一个链表,当使用一次 defer ,就会在链表的头结点插入一个执行结点,然后函数返回执行 defer 注册的操作时会依次取头结点执行,所以最后呈现的效果就是 “先入后出” 的执行顺序。

    0x01 实诚的建议

    defer 很好用,特别是用来关闭一切需要显式关闭的对象时,比如 file 的读写对象,golang 里协程的计数器 sync.WaitGroup ,在创建了一个类似的对象后为其注册一个 defer *.close() 调用绝对是一个很明智的选择。

    除此之外不建议用 defer,如果用 defer 来计算函数返回某些数值的话,往往会得到预期之外的结果。举个栗子:

    func defer_test1(){
        a, b := 1, 2
        defer add(a, b)
        a++
        b++
    }
    
    func add (a,b int){
        fmt.Println(a + b)
    }
    

    image-20211222161229673

    上述代码将输出 3 而不是使用 defer 设想的 5,出现这个问题的原因是因为 golang 中 defer 底层仍然采用的是值传递,当我们使用 defer 关键字时,它会向 defer 的执行队列里面拷贝引用的外层参数,比如上面这串代码中的 add 方法就是外层参数,那么内层参数 golang 又会怎么处理呢?上面说到 defer 的函数调用也是值传递的,它会将内层参数直接计算出来然后传递给外层参数,也就是 a = 1, b = 2 ,所以计算出来是 3

    这个时候就肯定有人想,既然是因为值传递引起的这个问题,那么我强制使用指针传递引用会发生什么呢?结果显而易见,引用传递的值将得到预期的 5

    func defer_test1(){
        a, b := 1, 2
        defer add(&a, &b)
        a++
        b++
    }
    
    func add (a,b *int){
        fmt.Println(*a + *b)
    }
    

    image-20211222161322227

    但是,你以为这就结束了嘛 -~-,当然没有!如果吧代码改成下面这样呢

    func defer_test1(){
        a, b := 1, 2
        defer fmt.Println(add(&a, &b))
        a++
        b++
    }
    
    func add (a,b *int) int{
        fmt.Println(*a + *b)
        return *a + *b
    }
    
    func add2(a, b int) int{
        return a + b
    }
    

    会输出 3 还是 5 ?答案是 3

    image-20211222162138057

    对于这个输出的理解,我是这样子理解的:对于 defer 来说外层参数为 fmt.Println 内层参数为 add 这里会拷贝 add 的值,也就是计算后的返回值 3。再写个代码检验一下。

    func defer_test1(){
    	a, b := 1, 2
    	defer add2(add(&a, &b), add(&a, &b))
    	a++
    	b++
    }
    
    func add (a,b *int) int{
        fmt.Println(*a + *b)
        return *a + *b
    }
    
    func add2(a, b int) int{
        return a + b
    }
    

    预期结果是 6 ,实际输出跟预期一致

    image-20211222171423031

    0x02 不需要建议

    那如果非要在结束时计算呢?我们可以使用匿名函数的方式来达到效果。

    func defer_test1(){
    	a, b := 1, 2
    	defer func (){
    		fmt.Println(a + b)
    	}()
    	a++
    	b++
    }
    

    输出:

    image-20211222191241460

    如上,就能够达到我们的目的。

    鸣谢

    Go 语言设计与实现》第 5.3 节

  • 相关阅读:
    memory addresses
    ddt ddl dml
    PHP Architecture
    disk_free_space
    SAPI
    Simple Mail Transfer Protocol
    AllowOverride None
    function &w(){}
    The History of Operating Systems
    are not called implicitly
  • 原文地址:https://www.cnblogs.com/Constantin/p/15720808.html
Copyright © 2020-2023  润新知