zoukankan      html  css  js  c++  java
  • 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 节

  • 相关阅读:
    LeetCode 11. Container With Most Water
    LeetCode 10 Regular Expression Matching
    LeetCode 9 Palindrome Number
    LeetCode 8 String to Integer (atoi)
    从ASP.NET Core 3.0 preview 特性,了解CLR的Garbage Collection
    HttpClient参观记:.net core 2.2 对HttpClient到底做了神马
    LeetCode 7 Reverse Integer
    《地久天长》观影笔记
    《小丑》观后感
    粒子群基本算法学习笔记
  • 原文地址:https://www.cnblogs.com/Constantin/p/15720808.html
Copyright © 2011-2022 走看看