Go defer(含面试题)

本文详细介绍了Go语言中defer的使用规则和常见应用场景,包括参数确定、后进先出执行顺序以及对返回值的影响。通过示例展示了defer在资源释放、流程控制和异常处理中的作用,并提供了相关面试题解析,帮助读者深入理解defer的工作原理。
摘要由CSDN通过智能技术生成

defer衍生出来的各种用法让人眼花缭乱,但其实Go官方对defer的规则只有三条

规则一:延迟函数的参数在defer语句出现时就已经确定了

func func1() {
	i := 0
	defer fmt.Println(i)//输出0
	i++
	defer fmt.Println(i)//输出1
	return
}
输出:
1
0

defer语句中的 fmt.Println() 参数i值在defer出现时就已经确定了,实际上是复制了一份。后面对变量i的修改不会影响fmt.Println()函数的执行,仍然打印0。

注意:对于指针类型的参数,此规则仍然适用,只不过延迟函数的参数是一个地址值,在这种情况下,defer后面的语句对变量的修改可能会影响延迟函数。

规则二:延迟函数按后进先出(LIFO)的顺序执行,即先出现的defer最后执行

定义defer类似与入栈操作,执行defer类似于出栈操作。

设计defer的初衷是简化函数返回时清理资源的动作,资源往往有依赖关系,比如先申请A,再根据A资源申请B,再根据B资源申请C,即申请顺序A-->B-->C。释放资源时需要反向进行。这就是把defer设计成LIFO的原因。

每申请一个用完需要释放的资源时,立即定义一个defer来释放资源是一个好习惯。

func func5() {
	for i := 0; i < 5; i++ {
		defer fmt.Print(i)
	}
}
输出:
43210

规则三:延迟函数可能操作主函数的具名返回值

当定义defer的函数(主函数)有返回值,返回值可能有名字(具名返回值),也可能没有名字(匿名返回值),延迟函数可能会影响返回值。

若要明白延迟函数是如何影响返回值的,只需要知道函数是如何返回的就够了

函数返回过程:关键字return不是一个原子操作,实际上return只代表汇编指令ret,即跳转程序执行。

比如 return i  ,实际上分两步执行,即先将 i 值存入栈中作为返回值,然后执行跳转,而defer的执行时机正是在跳转前,所以说defer执行时还是有机会操作返回值。

func deferFuncReturn() (result int) {
	i := 1
	defer func() {
		result ++
	}()
	return i
}

 该函数的return语句可以拆分成下面两行:

result = i
return

而延迟函数的执行正是在return之前,即加入defer后的执行过程如下:

result = i
result ++
return

defer使用场景:

defer语句用于延迟函数的调用,常用于关闭文件描述符、释放锁等资源释放场景。

1.释放资源(关闭文件句柄、数据库连接、停止定时器Tricker及关闭管道等资源清理资源)

m.mutex.Lock()
defer m.mutex.Unlock()

2.流程控制(控制函数执行顺序,配合wait.Group实现等待协程退出等)

var wg wait.Group
defer wg.Wait()
...

3.异常处理(与recover配合可以消除panic。且recover只能用于defer函数中)

defer func() { recover() } () 

defer面试题:

func func2() (val int) {
	val = 10
	defer func() {
		fmt.Printf("defer里面 val:%d\n",val)
		val += 2
	}()
	val++
	return val
}
func printFunc2()  {
	fmt.Println(func2())
}
func main() {
    prinFunc2()
}
输出:
defer里面 val:11
13

解释:主函数存在具名返回值val,执行步骤如下:

  1. 执行 val =10
  2. 创建defer(deferproc()负责把defer函数处理成_defer实例,并存入goroutine中的链表)
  3. 执行val++ (val=11)
  4. 执行return val ,可以分为两步,val = 11 和 return,但是return之前需要执行defer
  5. 执行defer(deferreturn()负责把defer从goroutine链表中的defer实例取出并执行)
  6. defer里面先打印 “defer里面 val:11”
  7.  defer里面执行val += 2(val=13)
  8. 再执行return,跳转程序执行到printFunc2()
  9. 执行 printFunc2()里面的打印fun2返回的val(此时val=13)
func func3() (val int) {
	val = 10
	defer func() {
		fmt.Printf("defer里面 val:%d\n",val)
		val += 2
	}()
	val++
	return 100
}
func printFunc3()  {
	fmt.Println(func3())
}
func main() {
    prinFunc3()
}
输出:
defer里面 val:100
102

 解释:具名返回值函数

func func4() (val int) {
	val = 0
	defer func() {
		fmt.Printf("func3 before val += 5:%d\n",val)
	}()
	val+=5
	defer func() {
		fmt.Printf("func3 after val += 5:%d\n",val)
	}()
	return val
}
func printFunc4(){
	fmt.Println(func4())
}
func main() {
    prinFunc4()
}
输出:
func3 after val += 5:5
func3 before val += 5:5
5

解释:defer先入后出。

func foo() int {
	var i int
	defer func() {
		i++
	}()
	return i
}
func main() {
	fmt.Println(foo())
}
输出:
1
0

解释:上面的函数有匿名返回值,返回一个局部变量i的值,同时defer函数也会操作这个局部变量,对于匿名函数来说,可以假定仍然有一个变量存储返回值,假定返回值未anony,上面的返回语句可以拆分为以下过程:

anony = i
i++
return

由于i是整型值,会将值赋值给anony,所以在defer语句中修改i值,虽然i值改变了,但是返回的是anony,即不会对函数返回值造成影响。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值