图解defer

1.defer的执行顺序:

多个defer出现的时候.它们是一个"栈"的关系.也就是先进后出.在一个函数中.写在前面的defer会比写在后面的defer调用的晚.

示例:

Go 复制代码
func main() {
    defer func1()
    defer func2()
    defer func3()
}

func func1() {
    fmt.Println("A")
}

func func2() {
    fmt.Println("B")
}

func func3() {
    fmt.Println("C")
}

每次执行到defer语句.并不会立刻执行.而是将defer后的表达式压入栈.如图所示.

所以func1() func2() func3() 会被一次压入栈中.在入栈的过程中程序不会执行defer后面的表达式.当mian函数执行完之后.被压入栈中的defer表达式会依次出栈并且执行.顺序是弹出一个.执行一次.然后弹出下一个.如图所示.

流程图:

2.defer与return谁先谁后:

Go 复制代码
func main() {
    returnAndDefer()
}

func deferFunc() int {
    fmt.Println("defer func called")
    return 0
}

func returnFunc() int {
    fmt.Println("return func called")
    return 0
}

func returnAndDefer() int {
    defer deferFunc()
    return returnFunc()
}

执行结果:

执行流程:

结论是return语句先执行.defer后面的语句后执行.

defer触发的出栈时机是当前函数的作用域结束.而return语句作为当前函数的最后一条语句显然是在函数结束之前需要执行完的语句.所以在return语句完成动作之前不会触发defer出栈且执行defer之后的表达式语句.

3.函数返回值的初始化:

该点不属于defer本身.但是调用场景却与defer有联系.如func DeferFunc1(i int){}中的返回值t int.这个t会在函数起始处被初始化为对应类型的零值并且作用域为整个函数.如图所示.

DeferFunc1的函数原型有一个返回值t.当执行DeferFunc1(10)的时候.t也会被初始化为0.同时t为0的作用域在DeferFunc1的整个生命周期.直到最后return 2语句将t赋值为2.

示例:

Go 复制代码
func main() {
     DeferFunc1(10)
}

func DeferFunc1(i int) (t int) {
    fmt.Println("t =", t)
    return 2
}

执行结果:

执行流程:

证明只要声明函数的返回值变量的名称.就会在函数初始化为之赋值为0.而且在函数体作用域可见.

4.有名函数返回值遇见defer的情况:

示例:

Go 复制代码
func main() {
    fmt.Println(returnButDefer())
}

func returnButDefer() (t int) {
    defer func() {
       t = t * 10
    }()
    return 1
}

执行结果:

执行流程:

在没有defer的情况下.其实函数的返回与return一致.但是有了defer就不一样了.先return在进行defer.

5.defer遇见panic:

能够触发defer的情况是遇见return(或函数体到末尾)和遇见panic.针对defer遇见return.如图所示.

当遇到panic时.会遍历本协程的defer链表.并执行defer.在执行defer的过程中.如果遇到recover.则停止panic.返回recover处继续往下执行.如果没有遇到recover.则遍历完本协程的defer链表后.向stderr抛出panic信息.如图所示.

6.defer遇见panic.但是并不捕获异常的情况:

示例:

Go 复制代码
func main() {
    defer_call()
}

func defer_call(){
    defer func() {
       fmt.Println("defer:panic之前1")
    }()

    defer func() {
       fmt.Println("defer:panic之前2")
    }()
    
    panic("异常内容")
    
    defer func() {
       fmt.Println("defer:panic之后.永远执行不到")
    }()
}

执行结果:

执行流程:

所以在panic之后的defer无法被触发.因为执行语句并没有将最后一个defer压栈.

7.defer遇见panic.并捕获异常:

示例:

Go 复制代码
func main() {
    defer_call()
}

func defer_call() {
    defer func() {
       fmt.Println("defer:panic之前1.捕获异常")
       if err := recover(); err != nil {
          fmt.Println(err)
       }
    }()

    defer func() {
       fmt.Println("defer:panic之前2.不捕获")
    }()

    panic("异常内容")

    defer func() {
       fmt.Println("defer:panic之后.永远执行不到")
    }()
}

执行结果:

执行流程:

defer最大的功能是panic后依然有效.所以defer可以保证一些资源一定会被关闭.从而避免一些异常出现的问题.

8.defer中包含panic:

示例:

Go 复制代码
func main() {
    defer func() {
       if err := recover(); err != nil {
          fmt.Println(err)
       }else {
          fmt.Println("fatal")
       }
    }()
    
    defer func() {
       panic("defer panic")
    }()
    
    panic("panic")
}

执行流程:

panic仅有最后一个可以被recover捕获.触发panic("panic")后defer按顺序出栈执行.第一个被执行的defer中会有panic("defer panic")异常语句.这个异常会覆盖mian中的异常.最后这个异常被第二个执行的defer捕获.

9.defer下的函数参数包含子函数:

示例:

Go 复制代码
func main() {
    defer function(1, function(3, 0))
    defer function(2, function(4, 0))
}

func function(index int, value int) int {
    fmt.Println(index)
    return index
}

执行结果:

执行流程:

1).defer压栈function1.压栈函数地址 形参1 形参2(调用function3).打印3.

2).defer压栈function2.压栈函数地址 形参1 形参2(调用function4).打印4.

3).defer出栈function2.调用function.打印2.

4).defer出栈function1.调用function1.打印1.

天色沉靛蓝.

语雀地址https://www.yuque.com/itbosunmianyi/xg8vfe?

《Go.》 密码:xbkk 欢迎大家访问.提意见.

相关推荐
码匠许师傅19 分钟前
【C++三方组件】POCO:跨平台系统编程全家桶
开发语言·c++
bkspiderx1 小时前
基于Socket的广播通信及C语言实现详解
c语言·开发语言·socket·广播·socket的广播通信
小王C语言1 小时前
Qt常用控件:输入类控件,单行/多行输入框、下拉框、(日期)微调框、旋钮、滑动条
开发语言·qt
ESDWAN1 小时前
SD-WAN 主备切换为什么仍然会断网?从检测机制到会话恢复分析
开发语言·php
1001101_QIA1 小时前
.exe does not seem to be a Qt executable.
开发语言·qt
小蒜学长1 小时前
基于Spring Boot+Vue3的在线医疗咨询系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·在线问诊·在线医疗咨询系统
echome8882 小时前
Python 异常处理:try、else、finally 的正确搭配
开发语言·前端·python·异常处理·编程技巧
云和数据.ChenGuang2 小时前
Python 单继承与多继承深度剖析:从 MRO 到实战取舍
开发语言·人工智能·python·langchain
陈随易2 小时前
下载神器!Motrix 停更三年后重写回归
前端·后端·程序员
每天一道题2 小时前
CAP、Raft 与多数派:分布式系统如何达成一致?
分布式·后端·微服务