乐趣区

关于golang:Go面试题一聊聊你理解的defer关键字

大家好,我是大道哥。

defer 关键字是咱们工作中常常用到的 go 语言个性,也是面试官比拟青眼的一个知识点,明天通过这篇文章带各位道友彻底把握它。

defer 两大个性

defer 是 golang 中的一个关键字,它次要具备两大个性:

  • 提早调用 : 在以后函数执行实现后调用执行。
func f1(){defer fmt.Println("hello world")

    fmt.Println("hello defer!")
}

输入后果:

$ go run main.go
hello 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.go
hello 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.go
1
5
0

张三道友心想:卧槽,开什么玩笑,难道不是 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++  //defer
ret  //r=1

对于 f2 来讲

func f2() (r int) {
    t:=5
    defer func() {t = t+5}()
    return t
}

// 执行过程
t:=5
r:=t
t=t+5 //defer
ret  //r=5

对于 f3() 来讲,在 defer 的时候传参 r,其实是一个值拷贝。

所以 defer 中对 r 的批改并不会影响返回值后果,助于了解把 r 换成 t,后果是等同的,即等效为

func f3() (r int) {defer func(t int) {t = t+5}(r)
    return 0
}

// 执行过程
r:=0
t = r,t = t +5 //defer
ret // 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.go
enter:  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.go
func1(hello) = hello, jack!

面试点总结

  • defer 的两大个性
  • defer 与 return 的执行程序
  • defer 的利用场景

后语

如果大家对本文提到的面试技术点有任何问题,都能够在评论区进行回复哈,咱们独特学习,一起提高!

关注公众号 [简道编程],每天一个后端技术面试点

本文由博客一文多发平台 OpenWrite 公布!

退出移动版