• 面试题之golang中的defer


    一、题目

    趋势科技一面golang软开实习
    面试官:说一下你对defer的理解和使用注意事项

    二、defer示例

    1、defer执行顺序

    多个defer出现,前后执行呈栈的关系,先进后出,程序流程中前面的defer比后面的defer调用的晚。另外defer后边只能跟函数。

    package main
    
    import "fmt"
    
    func main() {
        defer func1()
        defer func2()
        defer func3()
    }
    
    func func1() {
        fmt.Println("A")
    }
    
    func func2() {
        fmt.Println("B")
    }
    
    func func3() {
        fmt.Println("C")
    }
    

    执行结果

    C
    B
    A
    

    2、defer与return

    return之后的语句先执行,defer后的语句后执行

    package main
    
    import "fmt"
    
    func deferFunc() {
        fmt.Println("defer func called")
    }
    
    func returnFunc() {
        fmt.Println("return func called")
    }
    
    func returnAndDefer() int {
        // 后执行
        defer deferFunc()
        // 先执行
        return returnFunc()
    }
    
    func main() {
        returnAndDefer()
    }
    

    执行结果

    return func called
    defer func called
    

    3、defer与无命名返回值函数

    如果函数的返回值是无名的(不带命名返回值),则go语言会在执行return的时候会执行一个类似创建一个临时变量作为保存return值的动作。

    package main
    
    import "fmt"
    
    //无命名返回值
    func test() int {
    	var i int
    	defer func() {
    		i++
    		//作为闭包引用的话,则会在defer函数执行时根据整个上下文确定当前的值。i=2
    		fmt.Println("defer1", i)
    	}()
    	defer func() {
    		i++
    		//作为闭包引用的话,则会在defer函数执行时根据整个上下文确定当前的值。i=1
    		fmt.Println("defer2", i)
    	}()
    	// 先执行return i, 把i的值给到一个临时变量,作为函数返回值
    	return i
    }
    
    func main() {
    	// defer 和 return之间的顺序是先返回值, i=0,后defer
    	fmt.Println("test: ", test())
    }
    

    执行结果

    defer2 1
    defer1 2
    test: 0
    

    执行顺序为return语句->defer2->defer1->返回值。defer2先于defer1执行
    因此执行逻辑可以看做:
    return先执行,负责把结果写入返回值中,接着多个defer按照先进后出的顺序开始调用执行一些收尾工作,最后函数携带这个返回值退出。

    一般认为函数中执行到return,就直接函数生命周期结束,return的返回值就是函数返回值。但是由于defer语句的存在,return执行可以看做分为了两个步骤:

    • 赋值:由于返回值没有命名,所以默认指定了一个临时变量,比如tmp:=i
    • 返回:真正函数返回的是tmp,而后续defer语句对i的修改,不会影响到tmp

    4、defer与命名返回值函数

    命名返回值的函数,由于返回值在函数定义的时候已经将该变量进行定义,在执行return的时候会先执行返回值保存操作,而后续的defer函数会改变这个返回值,虽然defer是在return之后执行的,但是由于使用的函数定义的这个变量,所以执行defer操作后对该变量的修改,进而最终会影响到函数返回值。

    package main
     
    import "fmt"
    
    func test() (i int) { //返回值命名i
        defer func() {
            i++
            fmt.Println("defer1", i)
        }()
        defer func() {
            i++
            fmt.Println("defer2", i)
        }()
        return i
    }
    
    func main() {
        fmt.Println("test:", test())
    }
     
    

    执行结果:

    defer2 1
    defer1 2
    test: 2
    

    defer1,defer2的时候都可以修改变量i

    5、defer与panic

    正常情况下,defer遇到return或者函数执行流程到达函数体末尾会将进入栈的defer出栈并以此执行,同样遇到panic语句也是。
    遇到panic的时候,会遍历并将已经进栈的defer出栈并执行,但是对于程序流程中panic之后的defer就不会进栈。在defer出栈执行的过程中,遇到recover则停止panic,如果没有recover捕获panic,则执行完所以defer之后,抛出panic信息。

    package main
    
    import (
    	"fmt"
    )
    
    func test() {
    
    	defer func() { fmt.Println("defer: panic 之前0, 不捕获") }()
    
    	defer func() {
    		fmt.Println("defer: panic 之前1, 捕获异常")
            // 捕获异常信息
    		if err := recover(); err != nil {
    			// 输出panic中的错误信息
    			fmt.Println(err.(string))
    		}
    	}()
        // 正常进栈
    	defer func() { fmt.Println("defer: panic 之前2, 不捕获") }()
    
    	//触发defer出栈
    	panic("触发异常")
       
        // 由于在panic之后,不会在执行
    	defer func() {
    		fmt.Println("defer: panic 之后, 永远执行不到")
    	}()
    }
    
    func main() {
    	test()
            // 由于存在recover捕获panic,main函数流程则正常执行
    	fmt.Println("main 正常结束")
    }
    

    执行结果

    defer: panic 之前2, 不捕获
    defer: panic 之前1, 捕获异常
    触发异常
    defer: panic 之前0, 不捕获
    main 正常结束
    

    牛客网相关题目:体温异常

  • 相关阅读:
    sshd服务防止暴力破解
    使用秘钥ssh登录远程服务器
    SSH配置文件详解
    WinForm、wpf、silverlight三者关系
    silverlight 和winform的结合使用
    IIS在W7下使用
    c#多线程
    Silverlight的Socket通信
    wcf和webservice区别
    aspx向silverlight传值
  • 原文地址:https://www.cnblogs.com/welan/p/16330678.html
Copyright © 2020-2023  润新知