一. go 常见控制结构实现原理之 defer

一.

  1. defer语句用于延迟函数的调用,每次defer都会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行,示例题
//输出1
//defer语句中的函数调用的参数是在defer语句执行时就确定的,而不是在defer函数实际执行时才确定的
func deferFuncParameter() {
    var aInt = 1
    defer fmt.Println(aInt)
    aInt = 2
    return
}

//返回2,一个具名返回值result,返回i以后执行defer进行累加
//1. 首先,函数定义了一个名为result的返回值变量,初始值为0。
//2.然后,函数内部创建了一个局部变量i,并赋值为1。
//3.接着,函数遇到了return i语句,此时它会将i的值赋给result变量,也就是说result变为1。
//4.紧接着,函数执行defer语句中的匿名函数,该函数会将result变量递增1,也就是说result变为2。
//5.最后,函数携带当前的result变量作为返回值退出,也就是说它返回了2。
func deferFuncReturn() (result int) {
    i := 1
    defer func() {
       result++
    }()
    return i
}
func TestExecute1(t *testing.T) {
	//4
	println(MyFun1(1))
	//1
	println(MyFun2(1))
	//3
	println(MyFun3(1))
}

//具名反参,因为r是具名反参,在函数执行时已经被定义出来了
//内部对r的多次操作始终是基于一个地址值进行的,
func MyFun1(i int) (r int) {
	r = i
	defer func() {
		r += 3
	}()
	return r
}

func MyFun2(i int) int {
	//1.创建t变量,赋值为i
	t := i
	//2.将defer压入栈中,但是还未执行
	defer func() {
		t += 3
	}()
	//3.返回变量t,此时t与i相等
	//由于是未命名反参,执行defer时内部"t+=3"
	//与此处t是两个不相干的变量,所以最终该函数返回1
	return t
}

func MyFun3(i int) (r int) {
	//1.将defer压入栈中
	defer func() {
		r += i
	}()

	//2.返回2,将2赋值给变量r
	//然后执行defer,将i再累加到r上
	//最终该函数返回3
	return 2
}
  1. golang中总结defer的行为规则有三条
  1. 规则一:延迟函数的参数在defer语句出现时就已经确定下来了
  2. 规则二:延迟函数执行按后进先出顺序执行,即先出现的defer最后执行
  3. 规则三:延迟函数可能操作主函数的具名返回值

规则一

  1. 规则一:延迟函数的参数在defer语句出现时就已经确定下来了, 示例
func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}
  1. defer语句中的fmt.Println()参数i值在defer出现时就已经确定下来,实际上是拷贝了一份。后面对变量i的修改不会影响fmt.Println()函数的执行,仍然打印”0
  2. 对于指针类型参数,规则仍然适用,只不过延迟函数的参数是一个地址值,这种情况下,defer后面的语句对变量的修改可能会影响延迟函数

规则二

  1. 规则二:延迟函数执行按后进先出顺序执行,即先出现的defer最后执行
  2. 可以简单理解为: 定义defer类似于入栈操作,执行defer类似于出栈操作
  3. 设计defer的初衷是简化函数返回时资源清理的动作,资源往往有依赖顺序,比如先申请A资源,再跟据A资源申请B资源,跟据B资源申请C资源,即申请顺序是:A—>B—>C,释放时往往又要反向进行。这就是把deffer设计成FIFO的原因。

规则三

  1. 规则三:延迟函数可能操作主函数的具名返回值
  2. 关键字return不是一个原子操作,return只代理汇编指令ret,即将跳转程序执行。比如语句return i,实际上分两步进行,即将i值存入栈中作为返回值,然后执行跳转,而defer的执行时机正是跳转前,所以说defer执行时还是有机会操作返回值的

二. defer实现原理

  1. 查看defer源码,内部存在指向defere后跟随的函数的指针属性fn,与连接多个defer的指针属性link, 可以看为一个FIFO链表
type _defer struct {
    sp      uintptr   //函数栈指针
    pc      uintptr   //程序计数器
    fn      *funcval  //函数地址
    link    *_defer   //指向自身结构的指针,用于链接多个defer
}
  1. 每次声明一个defer时就将defer插入到单链表表头,每次执行defer时就从单链表表头取出一个defer执行, 函数返回前执行defer则是从链表首部依次取出执行

三. 问题总结

  1. 查看defer底层结构体,实际是一个FIFO链表, 多个defer时会用内部的link连接,使用defer有三条规则
  1. 规则一:延迟函数的参数在defer语句出现时就已经确定下来了
  2. 规则二:延迟函数执行按后进先出顺序执行,即先出现的defer最后执行
  3. 规则三:延迟函数可能操作主函数的具名返回值
  1. 一个函数中先执行defer还是return,执行是先执行defer,但是如果return会返回结果时要注意, 返回的的匿名变量,还是具名变量,如下,如果是匿名变量会将值保存在一个defer是看不到这个临时对象中,而名返回值就保存在已命名的变量中,所以defer中可以修改具名返回值
func increaseA() int {
	var i int
	defer func() {
		i++
	}()
	return i
}
func increaseB() (r int) {
	defer func() {
		r++
	}()
	return r
}
func main() {
	fmt.Println(increaseA()) //0
	fmt.Println(increaseB()) //1
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值