• golang defer


    一、先看一个例子,查看defer的效果

    func foo() int {
        i := 0
        defer fmt.Println("in defer :", i)
        i = 1000
        fmt.Println("in foo:", i)
        return i+24
    }
    

    这段代码打印出:

    in foo: 1000
    in defer : 0
    in main func: 1024
    

    变量i初始化为0defer指定fmt.Println函数延迟到return后执行,最后main函数调用foo打印返回值。

    二、一个函数中多个defer的执行顺序是什么?

    defer关键字会使其以下的代码先执行后再执行它指定的函数,包括其下的defer语句也会比其先执行,依此类推。

    这个顺序非常必要,因为在函数中,后面定义的对象可能依赖前面的对象,否则如果先出现的defer执行了,很可能造成后面的defer执行的时候出现异常。

    所以,Go语言设计defer的时候是按先进后出的顺序执行的

    func foo() {
        i := 0
        defer func() {
            i--
            fmt.Println("第一个defer", i)
        }()
    
        i++
        fmt.Println("+1后的i:", i)
    
        defer func() {
            i--
            fmt.Println("第二个defer", i)
        }()
    
        i++
        fmt.Println("再+1后的i:", i)
    
        defer func() {
            i--
            fmt.Println("第三个defer", i)
        }()
    
        i++
        fmt.Println("再+1后的i:", i)
    }
    

    运行后可以看到:

    +1后的i: 1
    再+1后的i: 2
    再+1后的i: 3
    第三个defer 2
    第二个defer 1
    第一个defer 0

    三、当传递参数给defer指定的函数时,函数延迟执行,那么参数值会是多少?

    defer指定的函数的参数在 defer 时确定,但,这只是一个总结,真正的原因是, Go语言除了map、slice、chan都是值传递

    func foo() {
        i := 0
        defer func(k int) {
            fmt.Println("第一个defer", k)
        }(i)
    
        i++
        fmt.Println("+1后的i:", i)
    
        defer func(k int) {
            fmt.Println("第二个defer", k)
        }(i)
    
        i++
        fmt.Println("再+1后的i:", i)
    
        defer func(k int) {
            fmt.Println("第三个defer", k)
        }(i)
    
        i++
        fmt.Println("再+1后的i:", i)
    }
    

    得到的结果:

    +1后的i: 1
    再+1后的i: 2
    再+1后的i: 3
    第三个defer 2
    第二个defer 1
    第一个defer 0 

    可能会有人觉得有一点出乎预料,i在return时不是已经被计算到3了吗?,为什么延迟执行的defer指定的函数里的i不是3呢?

    defer关键字指定的函数是在return后执行的,这很容易让人想象在return后调用函数。

    但是,defer指定的函数是在当前行就调用了的,只是延迟return后执行,而不等同于“移动”到return后执行,因此调用时传递的是当前的参数的值

    四、传递指针参数会是什么情况?

    如果希望defer指定的的函数参数的值是经过后面的代码处理过的,可以传递指针参数给defer指定的函数。

    func foo() {
        i := 0
        defer func(k *int) {
            fmt.Println("第一个defer", *k)
        }(&i)
    
        i++
        fmt.Println("+1后的i:", i)
    
        defer func(k *int) {
            fmt.Println("第二个defer", *k)
        }(&i)
    
        i++
        fmt.Println("再+1后的i:", i)
    
        defer func(k *int) {
            fmt.Println("第三个defer", *k)
        }(&i)
    
        i++
        fmt.Println("再+1后的i:", i)
    }
    

    运行后得到:

    +1后的i: 1
    再+1后的i: 2
    再+1后的i: 3
    第三个defer 3
    第二个defer 3
    第一个defer 3
    

    五、defer会影响返回值吗?

    在开头的第一个例子中可以看到,defer是在foo执行完,main里打印返回值之前执行的,但是没有影响到main里的打印结果。

    这还是因为相同的原则 Go语言除了map、slice、chan都是值传递

    func main() {
    
        fmt.Println("foo1 return :", foo1())
        fmt.Println("foot return :", foo2())
    
    }
    
    func foo1() int {
    
        i := 0
    
        defer func() {
            i = 1
        }()
    
        return i
    }
    
    func foo2() map[string]string {
    
        m := map[string]string{}
    
        defer func() {
            m["a"] = "b"
        }()
    
        return m
    }
    

    运行后,打印出: 

    foo1 return : 0
    foot return : map[a:b]  

    两个函数不同之处在于的返回值的类型,foo1中,int类型return后,defer不会影响返回结果,但是在foo2中map类型是引用传递,所以defer会改变返回结果。

    这说明,在return时,除了map、slice、chan,其他类型return时是将值拷贝到一个临时变量空间,因此,defer指定的函数内对函数内的变量的操作不会影响返回结果的。

    还有一种情况,给函数返回值申明变量名,,这时,变量空间是在函数执行前申明出来,return时只是返回这个变量空间的内容,因此defer能够改变返回值

    例如,改造一下foo1函数,给它的返回值申明一个变量名i

    func foo1() (i int) {
    
    	defer func() {
    		i = i + 4
    
    	}()
    
    	return i
    }
    

    返回结果如下:

    foo1 return : 4
    

    返回值被defer指定的函数修改了。

    六、defer在panic和recover处理上的使用

    在Go语言里,defer有一个经典的使用场景就是recover.

    在函数执行过程中,有可能在很多地方都会出现panicpanic后如果不调用recover,程序会退出,为了不让程序退出,我们需要在panic后调用recover,但,panic后的代码不会执行,recover是不可能在panic后调用,然而panic所在的函数内defer指定的函数可以执行,所以recover只能在defer指定的函数中被调用,并且只需要在1个defer指定的函数中处理。

    例如:

    func panicfunc() {
        defer func() {
            fmt.Println("before recover")
            recover()
            fmt.Println("after recover")
        }()
    
        fmt.Println("before panic")
        panic(0)
        fmt.Println("after panic")
    }
    

    打印出:

    before panic
    before recover
    after recover
  • 相关阅读:
    html 知识
    mysql use mysql hang
    微信机器人 简化版
    Tk::Table
    好友消息和群消息区别
    完整的微信登陆 接收消息流程
    Python OOP知识积累
    Python OOP知识积累
    JSTL EL 详解
    JSP中的EL表达式详细介绍
  • 原文地址:https://www.cnblogs.com/wuchangblog/p/16390904.html
Copyright © 2020-2023  润新知