• golang的defer踩坑汇总


    原文链接:http://www.zhoubotong.site/post/50.html

    defer语句用于延迟函数调用,每次会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行。延迟函数可以有参数:

    • 延迟函数的参数在defer语句出现时就已确定下来(传值的就是当前值)

    • return先赋值(对于命名返回值),然后执行defer,最后函数返回

    • 延迟函数执行按后进先出顺序执行

    • 延迟函数可操作主函数的变量名返回值(修改返回值)

    • defer后面的表达式可以是func或者是method的调用,如果defer的函数为nil,则会panic

    日常开发中,使用不当很容易造成意外的“坑”。下面我整理了下常规使用场景下,defer的问题可能的踩坑汇总。

    释放资源

    defer语句用于延迟函数调用,每次会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行。延迟函数可以有参数:

    • 延迟函数的参数在defer语句出现时就已确定下来(传值的就是当前值);

    • 延迟函数执行按后进先出顺序执行;

    • 延迟函数可操作主函数的具名返回值(修改返回值);

    defer 语句正好是在函数退出时执行的语句,所以使用 defer 能非常方便地处理资源释放、句柄关闭等问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package main
    import (
        "fmt"
        "os"
    )
    func fileSize(filename string) int64 {
        f, err := os.Open(filename)
        if err != nil {
            return 0
        }
        // 延迟调用Close, 此时Close不会被调用
        defer f.Close()
        info, err := f.Stat()
        if err != nil {
            // defer机制触发, 调用Close关闭文件
            return 0
        }
        size := info.Size()
        // defer机制触发, 调用Close关闭文件
        return size
    }
    func main() {
        fmt.Println(fileSize("demo.txt"))
    }

    变量捕获

    defer中的变量会被提前捕获,后续的修改不会影响到已捕获的值,举个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package main
    import (
        "fmt"
    )
    func main() {
        i := 0
        defer fmt.Println("Defer运行值:", i)
        i = 10 // 这里虽然修改了值,但是不会影响上面的i值
        fmt.Println("最后输出值:", i)
    }

    结果defer语句中打印的值是修改前的值。:

    最后输出值: 10

    Defer运行值: 0

    变量名返回值

    在defer中修改具体变量名返回值时,会影响到函数的实际返回值,继续举个例子:

    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
    package main
     
    import (
        "fmt"
    )
     
    func ShowDefer() {
        fmt.Println("最后输出值:", deferValue())
    }
    func deferValue() (ret int) { // 注意这里返回res变量值
        ret = 0
     
        defer func() { // 会直接修改栈中对应的返回值
            ret += 10
            fmt.Println("Defer 运行值:", ret)
        }()
        ret = 2
        fmt.Println("Ret重置值:", ret)
        return //返回的ret最后是 其实是本次2+上面的ret+=10的结果
    }
     
    func main() {
        ShowDefer()
    }
     
    //Ret重置值: 2
    //Defer 运行值: 12
    //最后输出值: 12

    非变量名返回值

    当函数为非具体名返回值时,defer无法影响返回值(因在return时,对应返回值已存入栈中),继续举个例子:

    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
    package main
     
    import (
        "fmt"
    )
     
    func ShowDefer() {
        fmt.Println("最后输出值:", deferValue())
    }
    func deferValue() int { // 非命名变量返回
        ret := 0
        defer func() {
        ret += 10
        fmt.Println("Defer 运行值:", ret)
    }()
        ret = 2
        return ret // 这里直接返回ret2
    }
     
    func main() {
        ShowDefer()
    }
     
    //Defer 运行值: 12
    //最后输出值: 2

    经过上面的实践理解,我们来看下下面的笔试题:

    笔试题一

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package main
     
    import "fmt"
     
    func f() (result int) {
        defer func() {
        result *= 7
    }()
        return 3
    }
    func main() {
        fmt.Println(f())
    }

    问题解析:这里return先给result赋值为3,之后执行defer,result变为21,最后返回21。

    笔试题二

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package main
     
    import "fmt"
     
    func f() int {
        result := 3
        defer func() {
            result *= 7
        }()
        return result
    }
     
    func main() {
        fmt.Println(f())
    } 

    问题解析:这里return确定返回值3,之后defer才修改result,最后函数返回return确定的返回值3。

    笔试题三

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package main
     
    import "fmt"
    // 多个defer
    func multiDefer() {
        for i := 3; i > 0; i-- {
        defer func(n int) {
            fmt.Print(n, " ")
        }(i)
        }
     
        for i := 33; i > 30; i-- {
            defer fmt.Print(i, " ")
        }
    }
     
    func main() {
        multiDefer()
    }  

    问题解析:多个defer函数,按顺序逆序执行,这里输出31 32 33 1 2 3 。

    笔试题四

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package main
     
    import "fmt"
     
    var fun func() string
     
    func main() {
        fmt.Println("hello monkey")
        defer fun()
    } 

    问题解析:由于这里的defer指定的func为nil,所以会panic 。

    笔试题五

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package main
     
    import "fmt"
     
    func main() {
        for i := 3; i > 0; i-- {
            defer func() {
                fmt.Print(i, " ")
                }()
        }
    } 

    问题解析:这里是极度容易踩坑的地方,由于defer这里调用的func没有参数,等执行的时候,i已经为0(按3 2 1逆序,最后一个i=1时,i--的结果最后是0),所以这里输出3个0 。

    如果还不太好理解?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package main
     
    import "fmt"
     
    func main() {
        for i := 3; i > 1; i-- { // 循环满足条件的是 3 2,
            defer func() { // 因为func 没有参数,defer运行最后i--即 2-- 结果为 1
                fmt.Print(i, " ") // 循环2次 结果均为 1
            }()
        }
    }//输出 1 1  

    按照常规的思维理解应该是这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package main
     
    import "fmt"
     
    func main() {
        for i := 3; i > 0; i-- {
        defer func(i int) {
            fmt.Print(i, " ")
            }(i)
        }
    } 
    感兴趣的朋友可以细细品下。


      

      

      

      

     
  • 相关阅读:
    云服务器利用Docker搭建sqli-labs靶场环境
    深度思考:ThreadLocal是否存在内存泄漏问题,如何防止内存泄漏?
    Ajax访问本地服务器案例
    编程笔记 Golang基础 045 math包
    C++基础第一章:头文件,输入符,输出符,控制符,换行符
    【模型剪枝】|Learning Efficient Convolutional Networks through Network Slimming
    【Java面向对象】封装的认识与实现
    实战Spring Boot集成quartz任务调度框架
    为UE和Unity开发者准备的Godot指南
    电源模块频率测试有哪些方法?纳米软件分享
  • 原文地址:https://www.cnblogs.com/phpper/p/16389393.html