Golang中的defer

面试常问之defer()的执行次序

情形1

go 复制代码
package main

func main() {

    defer print(123)
	defer_call()
	defer print(789) //panic之后的代码不会被执行
	print("不会执行到这里")
}

func defer_call() {
	defer func() {
		print("打印前")
	}()
	defer func() {
		print("打印中")
	}()

	defer print("打印后")

	panic("触发异常")

    defer print(666)   //IDE会有提示: Unreachable code
    
}

结果为:

go 复制代码
打印后打印中打印前123panic: 触发异常

goroutine 1 [running]:
main.defer_call()
	/Users/shuangcui/explore/panicandrecover.go:19 +0xe5
main.main()
	/Users/shuangcui/explore/panicandrecover.go:6 +0x51

可见:

  • panic之后的defer()不会被执行
  • panic之前的defer(),按照先进后出的次序执行,最后输出panic信息

(defer机制底层,是用链表实现的一个栈)

再如:

go 复制代码
func main() {

	fmt.Println(123)

	defer fmt.Println(999)

	subfunc()

}

func subfunc() {

	defer fmt.Println(888)

	for i := 0; i > 10; i++ {
		fmt.Println("当前i为:", i)
		panic("have a bug")
	}

	defer fmt.Println(456)

}

结果为:

go 复制代码
123
456
888
999

defer会延迟到当前函数执行 return 命令前被执行, 多个defer之间按LIFO先进后出顺序执行


情形2 (在defer内打印defer之外的主方法里操作的变量)

go 复制代码
package main

import "fmt"

func main() {
	foo()
}

func foo() {
	i := 0
	defer func() {
		//i--
		fmt.Println("第一个defer", i)
	}()

	i++
	fmt.Println("+1后的i:", i)

	defer func() {
		//i--
		fmt.Println("第二个defer", i)
	}()

	i++
	fmt.Println("再+1后的i:", i)

	defer func() {
		//i--
		fmt.Println("第三个defer", i)
	}()

	i++
	fmt.Println("再再+1后的i:", i)

	i = i + 666

	fmt.Println("+666后的i为:", i)

}

输出为:

go 复制代码
+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 669
第二个defer 669
第一个defer 669

情形3 (在defer内外操作同一变量)

go 复制代码
package main

import "fmt"

func main() {
	foo()
}

func foo() {
	i := 0
	defer func() {
		i--
		fmt.Println("第一个defer", i)
	}()

	i++
	fmt.Println("+1后的i:", i)

	defer func() {
		i--
		fmt.Println("第二个defer", i)
	}()

	i++
	fmt.Println("再+1后的i:", i)

	defer func() {
		i--
		fmt.Println("第三个defer", i)
	}()

	i++
	fmt.Println("再再+1后的i:", i)

	i = i + 666

	fmt.Println("+666后的i为:", i)

}

输出为:

go 复制代码
+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 668
第二个defer 667
第一个defer 666

情形4! (发生了参数传递!---传递参数给defer后面的函数, defer内外同时操作该参数)

go 复制代码
package main

import "fmt"

func main() {
	foo2()
}

func foo2() {
	i := 0
	defer func(k int) {
		//k--
		fmt.Println("第一个defer", k)
	}(i)

	i++
	fmt.Println("+1后的i:", i)

	defer func(k int) {
		//k--
		fmt.Println("第二个defer", k)
	}(i)

	i++
	fmt.Println("再+1后的i:", i)

	defer func(k int) {
		//k--
		fmt.Println("第三个defer", k)
	}(i)

	i++
	fmt.Println("再再+1后的i:", i)

	i = i + 666

	fmt.Println("+666后的i为:", i)

}

输出为:

go 复制代码
+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 2
第二个defer 1
第一个defer 0

如果取消三处k--的注释, 输出为:

go 复制代码
+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 1
第二个defer 0
第一个defer -1

等同于:

go 复制代码
package main

import "fmt"

func main() {
	foo3()
}

func foo3() {
	i := 0
	defer f1(i)

	i++
	fmt.Println("+1后的i:", i)


	defer f2(i)

	i++
	fmt.Println("再+1后的i:", i)

	defer f3(i)
	i++
	fmt.Println("再再+1后的i:", i)

	i = i + 666

	fmt.Println("+666后的i为:", i)

}

func f1(k int) {
	k--
	fmt.Println("第一个defer", k)
}

func f2(k int) {
	k--
	fmt.Println("第二个defer", k)
}

func f3(k int) {
	k--
	fmt.Println("第三个defer", k)
}

defer指定的函数的参数在 defer 时确定,更深层次的原因是Go语言都是值传递。

情形5! (传递指针参数!---传递参数给defer后面的函数, defer内外同时操作该参数)

go 复制代码
package main

import "fmt"

func main() {

	foo5()
}

func foo5() {
	i := 0
	defer func(k *int) {
		fmt.Println("第一个defer", *k)
	}(&i)

	i++
	fmt.Println("+1后的i:", i)

	defer func(k *int) {
		fmt.Println("第二个defer", *k)
	}(&i)

	i++
	fmt.Println("再+1后的i:", i)

	defer func(k *int) {
		fmt.Println("第三个defer", *k)
	}(&i)

	i++
	fmt.Println("再再+1后的i:", i)

	i = i + 666

	fmt.Println("+666后的i为:", i)
}

输出为:

go 复制代码
+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 669
第二个defer 669
第一个defer 669

作如下修改:

go 复制代码
package main

import "fmt"

func main() {

	foo5()
}

func foo5() {
	i := 0
	defer func(k *int) {
		(*k)--
		fmt.Println("第一个defer", *k)
	}(&i)

	i++
	fmt.Println("+1后的i:", i)

	defer func(k *int) {
		(*k)--
		fmt.Println("第二个defer", *k)
	}(&i)

	i++
	fmt.Println("再+1后的i:", i)

	defer func(k *int) {
		(*k)--
		fmt.Println("第三个defer", *k)
	}(&i)

	i++
	fmt.Println("再再+1后的i:", i)

	i = i + 666

	fmt.Println("+666后的i为:", i)
}

输出为:

go 复制代码
+1后的i: 1
再+1后的i: 2
再再+1后的i: 3
+666后的i为: 669
第三个defer 668
第二个defer 667
第一个defer 666

总结一下

  • 如果传参进defer后面的函数(无论是闭包func(){}(i)方式还是子方法f(i)方式,或是直接跟如fmt.Println(i)),defer回溯时均以当时传参时i的值去计算

  • 反之,defer回溯时,以最后i的值带入计算;(参考下面的例子).

参考:

Go面试题答案与解析


几种写法之间的归类与区别

go 复制代码
package main

import "fmt"

func main() {
	rs := foo6()
	fmt.Println("in main func:", rs)
}

func foo6() int {
	i := 0
	defer fmt.Println("in defer :", i)

	//defer func() {
	//	fmt.Println("in defer :", i)
	//}()

	i = 1000
	fmt.Println("in foo:", i)
	return i+24
}

输出为:

go 复制代码
in foo: 1000
in defer : 0
in main func: 1024

如果改为:

go 复制代码
package main

import "fmt"

func main() {
	rs := foo6()
	fmt.Println("in main func:", rs)
}

func foo6() int {
	i := 0
	//defer fmt.Println("in defer :", i)
	defer func() {
		fmt.Println("in defer :", i)
	}()

	i = 1000
	fmt.Println("in foo:", i)
	return i+24
}

输出为:

go 复制代码
in foo: 1000
in defer : 1000
in main func: 1024

也可见,

go 复制代码
defer fmt.Println("in defer :", i)

相当于

go 复制代码
defer func(k int) {
		fmt.Println(k)
    }(i)

go 复制代码
func f(k int){
	fmt.Println(k)
}

这时的参数,都是传递时的值

而如

go 复制代码
	defer func() {
		fmt.Println("in defer :", i)
    }()

这时的参数,为最后return之前那一刻的值


defer会影响返回值吗?

函数的return value 不是原子操作, 在编译器中实际会被分解为两部分:返回值赋值return 。而defer刚好被插入到末尾的return前执行(即defer介于二者之间)。故可以在defer函数中修改返回值

go 复制代码
package main

import (
	"fmt"
)

func main() {
	fmt.Println(doubleScore(0))    //0
	fmt.Println(doubleScore(20.0)) //40
	fmt.Println(doubleScore(50.0)) //50
}
func doubleScore(source float32) (rs float32) {
	defer func() {
		if rs < 1 || rs >= 100 {
			//将影响返回值
			rs = source
		}
	}()
	rs = source * 2
	return
	//或者
	//return source * 2
}

输出为:

go 复制代码
0
40
50

再如:

go 复制代码
func main() {

    fmt.Println("foo return :", foo2())

}

func foo() map[string]string {

    m := map[string]string{}

    defer func() {
        m["a"] = "b"
    }()

    return m
}

输出为:

go 复制代码
foo return : map[a:b]

又如:

go 复制代码
package main


import "fmt"

func main() {
	fmt.Println("foo return :", foo())
}

func foo() int {
	i := 0

	defer func() {
		i = 10086
	}()

	return i + 5
}

输出为:

go 复制代码
foo return : 5

若作如下修改:

go 复制代码
func foo() (i int) {
	i = 0
	defer func() {
		i = 10086
	}()

	return i + 5
}

则返回为:

go 复制代码
foo return : 10086

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

return value拆解为两步: 确定value值,然后return..即如果return 后面是个方法或者复杂表达式,且有某个值i,会先计算.完成后defer再执行,如果defer里面也有对i的改动,是可以影响返回值的

(给函数返回值申明变量名, 这时, 变量的内存空间空间是在函数执行前就开辟出来的,且该变量的作用域为整个函数,return时只是返回这个变量的内存空间的内容,因此defer能够改变返回值)

defer不影响返回值,除非是map、slice和chan这三种引用类型,或者返回值定义了变量名


参考:

Golang研学:如何掌握并用好defer--存疑("引用传递"那里明显错误)

Golang中的Defer必掌握的7知识点

本文由mdnice多平台发布

相关推荐
customer0816 分钟前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
Yaml41 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
小码编匠2 小时前
一款 C# 编写的神经网络计算图框架
后端·神经网络·c#
AskHarries2 小时前
Java字节码增强库ByteBuddy
java·后端
佳佳_3 小时前
Spring Boot 应用启动时打印配置类信息
spring boot·后端
许野平4 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
BiteCode_咬一口代码5 小时前
信息泄露!默认密码的危害,记一次网络安全研究
后端
齐 飞6 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb