那些年,踩过多少个defer关键字的坑。快来带你看看defer的正确逻辑,通俗易懂的比喻,让你秒懂

奇怪的defer执行

defer和go一样都是Go语言提供的关键字。defer用于资源的释放,会在函数返回之前进行调用。
但是defer也有你意想不到的坑,掉进去也找不明头绪的那种。

先看看几个例子

例子1

func f() (result int) {
    defer func() {
        result++
    }()
    return 0
}

例子2

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

例子3

func f() (r int) {
    defer func(r int) {
          r = r + 5
    }(r)
    return 1
}

请读者思考一下,这几条代码的结果会是什么呢?

然后在自己运行一下,看到结果的你会不会有点奇怪呢?
例1的正确答案不是0,例2的正确答案不是10,如果例3的正确答案不是6…
例1的正确答案是1,例2的正确答案是5,如果例3的正确答案是1…

(以上引用书籍《深入解析Go》,书籍已在github上发布
关注后评论区扣1,发中文版链接!!秒发哦!!)

好了,接下来我给大家用通俗易懂的比喻解释一下

一、核心原理解析:快递发货三部曲

Go语言中的return xxx语句本质上是三个步骤的非原子操作,就像电商仓库的订单处理流程:

装箱封口(赋值返回值):将货物放入快递盒,贴上地址标签(将xxx赋值给返回值变量)
最终质检(执行defer):检查包裹是否牢固、补写保修卡(执行所有注册的defer函数)
物流发车(真正返回):包裹离开仓库,进入运输流程(执行return指令跳回调用方)
关键陷阱:质检员(defer)可能在封箱后拆开包裹调整内容,导致最终收到的货物与装箱时不同。

二、比喻场景:暗藏玄机的礼品包裹

场景1: 基础流程(无defer干扰)

func sendGift() string {  
    gift := "巧克力礼盒"  // 仓库取出巧克力  
    return gift        // 等价于:  
                       // 1. 返回值 = "巧克力礼盒"(装箱)  
                       // 2. 无defer(不质检)  
                       // 3. return(发车)  
}  
// 客户收到:巧克力礼盒 ✅  

场景2: defer篡改包裹内容

func sendGift() (result string) {  
    result = "巧克力礼盒"          // 装箱  
    defer func() {  
        result = "石头"           // 质检员调包  
    }()  
    return                     // 等价于:  
                                // 1. 返回值 = "巧克力礼盒"  
                                // 2. defer修改为"石头"  
                                // 3. return  
}  
// 客户收到:石头   

三、逻辑解析:改写开头的示例,正确答案如何而来

  1. 例子1
func f() (result int) {
    defer func() {
        result++
    }()
    return 0
}

可以改写成为:

func f() (result int) {
    result = 0
    result++ //defer执行
    return
}

所以答案是1

  1. 例子2
func f() (r int) {
     t := 5
     defer func() {
       t = t + 5
     }()
     return t
}

可以改写:

func f() (r int) {
     t := 5
     r = t
     t = t+5 //defer执行
     return
}

所以答案是5

  1. 例子3
func f() (r int) {
    defer func(r int) {
          r = r + 5
    }(r)
    return 1
}

可以改写:

func f() (r int) {
    r = 1
    // defer 传入非指针所以会复制一个复制体进去,不会改变原值
    defer func(r int) {
          r = r + 5
    }(r)
    return
}

所以答案是1

四、深度拆解:两种返回值类型的陷阱差异

  1. 匿名返回值 (易被篡改的临时包装盒)
func demo() int {  
    res := 10  
    defer func() { res += 5 }()  // 修改的是局部变量,非返回值  
    return res                   // 装箱后defer无法触及  
}  
// 返回10(装箱后质检员无权开箱)  
  1. 命名返回值 (敞开的快递盒)
func demo() (res int) {  
    res = 10  
    defer func() { res += 5 }()  // 直接操作快递盒内部  
    return                       // 装箱和质检可同步操作  
}  
// 返回15(质检员在发车前修改内容)  

操作权限示意图:

匿名返回值:[封闭盒] → 封箱后移交质检(defer只能摸盒子表面)
命名返回值:[开口盒] → 封箱后仍可伸手调整内容(defer能穿透修改)

四、避坑指南:安全使用defer的两大法则

  1. 隔离原则(封装黑盒)
  • 在defer中避免直接操作返回值变量,改用函数参数传递副本:
defer func(val int) {  
   // 使用val而非直接修改返回值  
}(res)  
  1. 时序控制(先验质检)
  • 关键资源释放(如文件关闭)写在defer之前,确保状态稳定:
file, _ := os.Open("data.txt")   
defer file.Close()  // 安全:关闭操作不会影响已读取的数据  

制作不易
如果觉得本篇文章对你有帮助的话,麻烦点赞关注收藏啊~~收藏起来!

有机会的话,可以关注我的Golang专栏,免费的哦,每天更新一点跟Golang相关的知识哦,或者想要解答疑问的可以在评论区提出,可以解答哦~
Golang专栏链接: Golang那些事专栏

你可能感兴趣的:(Golang,golang,后端)