背景

defer在golang中属于关键词,主要用于资源释放,会在函数返回之前被调用,但其中也包含了很多的坑。下面我们通过几个常见例子来进行相关的讲解。

例子

// e.g.1
func f1() (r int) {
	defer func() {
		r = r + 5
	}()
	return 1
}

// e.g.2
func f2() (r int) {
	t := 5
	defer func() {
		t = t + 5
	}()
	return t
}

// e.g.3
func f3() (r int) {
	t := 5
	defer func(r int) {
		r = r + 5
	}(r)
	return t
}

// e.g.4
func f4() {
	t := 5
	defer fmt.Println("t1 =", t)
	t = 4
	defer fmt.Println("t2 =", t)
}

我们先不直接说出其中运行的结果,我们都简单的针对上面的例子在心中计算一下,并想想是为什么,下面我在写出其中的结果.

原理讲解

以下为我们执行的实际结果

func main() {

    fmt.Println("f1() =", f1())
    fmt.Println("f2() =", f2())
    fmt.Println("f3() =", f3())
    f4()
    
    // Outputs:
    // f1() = 6
    // f2() = 5
    // f3() = 5
    // t2 = 4
    // t1 = 5

    return
}

上面的结果和你心中想到的是否一致?哈哈,如果一致的话相信你是懂defer的运行机制的,可以绕过本文了。有不懂或没弄对的地方的话,就继续往下看,我来一一给你讲解。

defer规则

我们来直接说一下defer的三个简单规则,详见官方文档

  • 规则1-延迟函数的参数在defer语句出现时就已经确定下来了
  • 规则2-延迟函数执行按后进先出顺序执行,即先出现的defer最后执行
  • 规则3-延迟函数可能操作主函数的具名返回值


    针对规则1,意思是defer后面的函数一在定义完以后,如果函数有传入值,则在那一刻就把值给固定下来了,这个规则能很好的解释上面提到的f4(),打印的t1=5,t2=4


    针对规则2,意思是说defer采用的是FILO(先进后出)的规则,用这个规则也能解释f4()里面的打印顺序问题,先打印t2再打印t1


    针对规则3,意思是主函数可能有返回值,而这个defer后面的函数可能会影响到主函数的返回值,这里我们就使用规则3来对上面的另外三个例子进行一一说明,f1()里面return 1,哦,我再顺便说一个利于你理解记忆的小诀窍,在return的时候首先赋值,然后执行defer,最后返回,也就是先r=1,然后执行defer里面r = r + 5,最后才是返回的r,打印f1() = 6,解释通了吧?哈哈;f2()里面带有一点迷惑性,但是我们直接利用上面的规则拆分,return的时候是先执行的r = t,也就是r=5,而defer执行的操作只是对t操作,和r没有半毛钱的关系,这样打印f2() = 5也就能解释了;f3()里面同上面规则也一样,先赋值,但是有一点是defer里面函数通过值传递,在值传递的时候已经是进行了值的拷贝操作,并没有把返回的值给更改掉,或者换个思路,把f3()里面的defer函数r换成其他变量,也许一眼就看懂了;

小结

  • defer定义的延迟函数参数在defer语句出时就已经确定下来了
  • defer定义顺序与实际执行顺序相反
  • return不是原子操作,执行过程是: 保存返回值(若有)-->执行defer(若有)-->执行ret跳转申请资源后立即使用defer关闭资源是好习惯

© 2019·蜀ICP备18036663号-1 · 本页总阅读量 · 本站总访问量 · 本站总访客数