大家好,我是大道哥。
defer关键字是咱们工作中常常用到的go语言个性,也是面试官比拟青眼的一个知识点,明天通过这篇文章带各位道友彻底把握它。
defer两大个性
defer是golang中的一个关键字,它次要具备两大个性:
- 提早调用: 在以后函数执行实现后调用执行。
func f1(){ defer fmt.Println("hello world") fmt.Println("hello defer!")}
输入后果:
$ go run main.gohello defer!hello world
- 后进先出: 多个defer函数时,执行程序为后进先出。
func f2(){ defer fmt.Println("hello 1!") defer fmt.Println("hello 2!") defer fmt.Println("hello 3!") fmt.Println("hello defer!")}
输入后果
$ go run main.gohello defer!hello 3!hello 2!hello 1!
defer与return的执行程序
defer与return的执行程序,是面试时常常考查的一点,须要道友们好好了解。
首先,咱们举个栗子,看如下状况下代码的输入后果。
func f1() (r int){ defer func(){ r++ }() return 0}func f2() (r int) { t:=5 defer func() { t = t+5 }() return t}func f3() (r int) { defer func(r int) { r = r+5 }(r) return 0}func main(){ fmt.Println(f1()) fmt.Println(f2()) fmt.Println(f3())}
倡议敌人们先思考下答案,再往后看。
$ go run main.go150
张三道友心想:卧槽,开什么玩笑,难道不是 0、10、5...
好的,上面咱们逐个剖析一下:
这里咱们须要先了解下return语句的执行程序。
return语句自身并不是一条原子指令,它会先给返回值赋值,而后再是返回,如下
func f4() (r int) { return 1}//执行过程:r:=1 //赋值ret //执行返回
而在含defer表达式时,函数返回的过程是这样的:
先给返回值赋值,而后调用defer表达式,最初再是返回后果
即对于f1()来讲
func f1() (r int){ defer func(){ r++ }() return 0}//执行过程:r:=0 //赋值r++ //deferret //r=1
对于f2来讲
func f2() (r int) { t:=5 defer func() { t = t+5 }() return t}//执行过程t:=5r:=tt=t+5 //deferret //r=5
对于f3()来讲,在defer的时候传参r,其实是一个值拷贝。
所以defer中对r的批改并不会影响返回值后果,助于了解把r换成t,后果是等同的,即等效为
func f3() (r int) { defer func(t int) { t = t+5 }(r) return 0}//执行过程r:=0t = r, t = t +5 //deferret // r=0
defer的利用场景
场景一:资源开释
咱们在代码中应用资源时如:关上一个文件,很容易因为遗记开释或者因为逻辑上的谬误导致资源没有敞开。这时候应用defer能够防止这种资源透露。无妨先看如下代码:
file,_ := os.Open("test.txt")//process为业务逻辑解决if err:=process(file);err!=nil { return}file.Close()
下面的代码即存在一个重大的问题,如 err!=nil 间接return后,会使得file.close() 敞开资源的语句没有执行,导致资源透露。
且在经验了一串业务逻辑解决编写后,咱们也很容易遗记敞开资源导致资源透露。所以应该牢记一个准则:在每个资源申请胜利的前面都加上defer主动清理,不论该函数都多少个return,资源都会被正确的开释。
正确的编写逻辑如下:
file,_ := os.Open("test.txt")defer file.Close()//process为业务逻辑解决if err:=process(file);err!=nil { return}
场景二:异样捕捉
Golang中对于程序中的异样解决,没有try catch,然而有panic和recover。 当程序中抛出panic时,如果没有及时recover,会导致服务间接挂掉,造成很重大的结果,所以咱们个别用recover来捕捉异样。
func main(){ defer func(){ if ok:=recover();ok!=nil{ fmt.Println("recover") } }() panic("error")}
下面两个场景是咱们必须要熟知的,当然还能够利用defer的个性优雅的实现一些相似于代码追踪、记录函数的参数和返回值等。
场景三: 代码追踪
咱们通过追踪程序进入或来到某个函数的信息,来测试此函数是否被执行。
func main(){ f1() f2()}func f1(){ defer trace_leave(trace_enter("f1()")) fmt.Println("f1()程序逻辑")}func f2(){ defer trace_leave(trace_enter("f2()")) fmt.Println("f2()程序逻辑")}func trace_enter(msg string) string{ fmt.Println("enter: ",msg) return msg}func trace_leave(msg string) { fmt.Println("leave: ",msg)}
输入后果如下:
$go run main.goenter: f1()f1()程序逻辑leave: f1()enter: f2()f2()程序逻辑leave: f2()
场景四: 打印函数的参数和返回值
某函数的执行后果不合乎预期,咱们能够应用defer来一步到位的打印函数的参数和返回值,而非多处打印调试语句。
func main(){ func1("hello")}func func1(str string) ( res string) { defer func() { fmt.Printf("func1(%s) = %s", str, res) }() res = fmt.Sprintf("%s, jack!",str) return}
输入后果:
$go run main.gofunc1(hello) = hello, jack!
面试点总结
- defer的两大个性
- defer与return的执行程序
- defer的利用场景
后语
如果大家对本文提到的面试技术点有任何问题,都能够在评论区进行回复哈,咱们独特学习,一起提高!
关注公众号[简道编程],每天一个后端技术面试点
本文由博客一文多发平台 OpenWrite 公布!