关于golang:Go-Quiz-从Go面试题看defer的注意事项第2篇

6次阅读

共计 2994 个字符,预计需要花费 8 分钟才能阅读完成。

面试题

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

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

本文的题目如下:

package main

func 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 main

import "fmt"

// 函数类型 FuncType
type FuncType func(int) int

// 定义变量 f1, 类型是 FuncType
var f1 FuncType = func(a int) int {return a}

// 定义变量 f, 类型是一个函数类型,函数签名是 func(int) int
// 在 main 函数里给 f 赋值,零值是 nil
var f func(int) int

func 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 main
    
    import "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 main

import "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/…
正文完
 0