面试题

这是Go Quiz系列的第5篇,是考查Go语言的defer语义,也是defer语义的第2篇。

没有看过defer第1篇的能够先回顾下:Go defer语义第1篇。

本文的题目如下:

package mainfunc bar() (r int) {    defer func() {        r += 4        if recover() != nil {            r += 8        }    }()        var f func()    defer f()    f = func() {        r += 2    }    return 1}func main() {    println(bar())}
  • A: 1
  • B: 7
  • C: 12
  • D: 13
  • E: 15

这道题相比于defer第1篇的题目,次要考查以下知识点:

  • defer的函数的值是什么时候确定的?留神:这里说的是函数值,不是函数的参数。
  • 如果函数的值是nil,那defer一个nil函数是什么后果?
  • 多个被defer的函数的执行先后顺序,遵循LIFO准则。
  • deferrecover联合能够捕捉panic
  • defer如果对函数的命名返回值参数做批改,有什么影响?

解析

函数值

下面提到了函数值的概念,对应的英文是function value。可能刚学习Go的同学还不太理解,我先解说下这个概念。

首先,函数和structint等一样,也是一个类型(type)。

咱们能够先定义一个函数类型,再申明一个该函数类型的变量,并且给该变量赋值。该函数类型变量的值咱们就能够叫做函数值。看上面的代码示例就高深莫测了。

package mainimport "fmt"// 函数类型FuncTypetype FuncType func(int) int// 定义变量f1, 类型是FuncTypevar f1 FuncType = func(a int) int { return a }// 定义变量f, 类型是一个函数类型,函数签名是func(int) int// 在main函数里给f赋值,零值是nilvar f func(int) intfunc main() {    fmt.Println(f1(1))    // 定义变量f2,类型是FuncType    var f2 FuncType    f2 = func(a int) int {        a++        return a    }    fmt.Println(f2(1))    // 给函数类型变量f赋值    f = func(a int) int {        return 10    }    fmt.Println(f(1))}

咱们平时实现函数的时候,通常是把函数的类型定义,函数变量和变量赋值一起做了。

函数类型的变量如果只申明不初始化,零值是nil,也就是默认值是nil。

通过下面的例子咱们晓得能够先定义函数类型,前面再定义函数类型的变量。

原理解析

咱们再回顾下官网文档怎么说的:

Each time a "defer" statement executes, the function value and parameters to
the call are evaluated as usual and saved anew but the actual function is not
invoked.

Instead, deferred functions are invoked immediately before the
surrounding function returns, in the reverse order they were deferred.

That is, if the surrounding function returns through an explicit return statement,
deferred functions are executed after any result parameters are set by that
return statement but before the function returns to its caller.

If a deferred function value evaluates to nil, execution panics when the function is
invoked, not when the "defer" statement is executed.

本题的代码里的bar()函数在return之前依照如下程序执行

执行执行后果
执行return 1把1赋值给函数返回值参数r
执行f()因为f的值在defer f()这句执行的时候就曾经确定下来是nil了,所以会引发panic
执行bar函数里第1个被defer的函数r先加4,值变为5,而后recover捕捉上一步的panic,r的值再加8,后果就是13
bar()返回r的值r的值是13,返回13。main里打印13

因而本题的运行后果是13,答案是D

总结

defer第1篇文章里咱们列出了defer六大准则,参考Go defer语义6大准则。

本文再总结下本题目波及的defer语义另外2大注意事项:

  • 被defer的函数值(function value)在执行到defer语句的时候就被确定下来了。

    package mainimport "fmt"func main() {    defer func() {        r := recover()        fmt.Println(r)    }()    var f func(int) // f没有初始化赋值,默认值是nil    defer f(1) // 函数变量f的值曾经确定下来是nil了    f = func(a int) {}}
  • 如果被defer的函数或办法的值是nil,在执行defer这条语句的时候不会报错,然而最初调用nil函数或办法的时候就引发panic: runtime error: invalid memory address or nil pointer dereference

思考题

思考上面这道题的运行后果是什么?大家能够在评论区留下你们的答案。也能够在我的wx公号发送音讯 defer2 获取答案和起因。

题目1:程序运行后果是什么?

package mainimport "fmt"func main() {    defer func() {        fmt.Print(recover())    }()    defer func() {        defer fmt.Print(recover())        defer panic(1)        recover()    }()    defer recover()    panic(2)}

开源地址

文章和示例代码开源地址在GitHub: https://github.com/jincheng9/go-tutorial

公众号:coding进阶

集体网站:https://jincheng9.github.io/

知乎:https://www.zhihu.com/people/thucuhkwuji

好文举荐

  1. 被defer的函数肯定会执行么?
  2. Go Quiz: 从Go面试题看slice的底层原理和注意事项
  3. Go Quiz: 从Go面试题看channel的注意事项
  4. Go Quiz: 从Go面试题看分号规定和switch的注意事项
  5. Go Quiz: 从Go面试题看defer语义的底层原理和注意事项
  6. Go有援用变量和援用传递么?map,channel和slice作为函数参数是援用传递么?
  7. new和make的应用区别是什么?
  8. 官网教程:Go泛型入门
  9. 一文读懂Go泛型设计和应用场景
  10. 一文读懂Go匿名构造体的应用场景

References

  • https://go101.org/quizzes/def...
  • https://github.com/jincheng9/...
  • https://github.com/jincheng9/...
  • https://golang.google.cn/ref/...