虽说比较常用,但是遇到一道面试题还是比较懵逼,总结一下~

面试题

下面代码输出什么?

package main

import "fmt"

func calc(index string, a, b int) int {
    ret := a + b
    fmt.Println(index, a, b, ret)
    return ret
}

func main() {
    a := 1
    b := 2
    defer calc("1", a, calc("10", a, b))
    a = 0
    defer calc("2", a, calc("20", a, b))
    b = 1
}

/* output

10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4
*/

关键知识点

  1. defer 是什么?

    defer 是 Go 语言提供的一种用于注册延迟调用的机制,每一次 defer 都会把函数压入栈中,当前函数返回前再把延迟函数取出并执行。

  2. 两种使用方式:
    1. 函数参数:作为函数参数,则在 defer 定义时就把值传递给 defer,并被缓存起来;
    2. 作为闭包引用:作为闭包引用的话,则会在 defer 函数真正调用时根据整个上下文确定当前的值。
    3. 核心:在 defer 语句里会不会改变返回值。
  3. return 不是原子操作:
    1. 赋值: 返回值 = xxx
    2. 空的 return defer 在 步骤 1 和 2 中间执行
  4. defer 进栈之前 执行参数(函数):defer 语句执行的时候,会直接得到所有参数,然后 defer 带着评估后的参数入栈

分析上边函数执行

  1. 根据知识点 4,先执行
calc("10", a, b) // a=1,b=2, 输出:10,1,2,3 并且 3 进入 defer 函数中
calc("20", a, b) // a=0,b=2, 输出:20,0,2,2 并且 2 进入 defer 函数中
  1. 先进后出
calc("2",a,2) // a=0 输出 2,0,2,2
calc("1", a,3)//a=1 输出 1,1,3,4
  1. 最终输出就定下来了

第二道 defer 面试题

坑: panic 输出位置

package main

import (
    "fmt"
)

func main() {
    defer_call()
}

func defer_call() {

    defer func() { fmt.Println("打印前") }()
    defer func() { fmt.Println("打印中") }()
    defer func() { fmt.Println("打印后") }()

    panic("触发异常")
}

output:

打印后
打印中
打印前
panic: 触发异常

知识点

  1. panic 异常,程序中断运行,并立即执行当前 goroutine 中 defer 函数,随后程序崩溃输出日志信息:panic
  2. fmt.Println() 及所有的 fmt 包输出,都是到 标准输出中 os.Stdout
  3. Go 中 os.Stdout 是无缓冲的
  4. 无缓冲:没有缓冲区,数据会立即读入或者输出到外存文件和设备上。标准错误 stderr 是无缓冲的,这样保证错误提示和输出能够及时反馈给用户,供用户排除错误。

参考

  1. os.Stdout is not buffered ?
  2. Go 问答 101
  3. https://studygolang.com/topics/9992