返回顶部

面试题之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 正常结束

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

posted on 2022-05-31 15:41  weilanhanf  阅读(525)  评论(2编辑  收藏  举报

导航