Go 语言 defer 在命名返回值 和 匿名返回值 函数中的表现不一样

go 复制代码
func f2() int {
	var a int
	defer func() {
		a++
	}()

	return a
}

func f3() (a int) {
	defer func() {
		a++
	}()

	return a
}

fmt.Println(f2()) //0

fmt.Println(f3()) //1

这是一个经典的 defer + 命名返回值 的陷阱,也是面试高频题。


🎯 核心结论

defer 可以修改命名返回值,但不能修改匿名返回值。

  • 匿名返回值 :return 时值已经固定,defer 修改的是局部变量,不影响返回值
  • 命名返回值 :return 时只是将返回值变量赋给结果,defer 可以继续修改这个变量

🔍 详细拆解

函数 f2():匿名返回值

go 复制代码
func f2() int {          // 返回值匿名
    var a int            // a = 0
    defer func() {
        a++              // ③ 修改局部变量 a(a变成1)
    }()
    return a             // ① 返回值 = a(0)
                         // ② 执行 defer
                         // ④ 返回 0(已经固定)
}

执行顺序:

  1. return a 把 a 的当前值(0) 作为返回值
  2. 执行 defer,a 变成 1
  3. 但返回值已经是 0,不会改变

结果:0


函数 f3():命名返回值

go 复制代码
func f3() (a int) {      // 返回值命名,a就是返回值变量
    defer func() {
        a++              // ③ 直接修改返回值变量
    }()
    return a             // ① 返回语句:把 a(0) 作为返回值
                         // ② 执行 defer,a变成1
                         // ④ 返回 1(返回值变量被改了)
}

执行顺序:

  1. return a 设置返回值 = a(此时 a=0)
  2. 执行 defer,a++ 把返回值变量 改成 1
  3. 函数返回 1

结果:1


📊 执行顺序图

复制代码
f2():                      f3():
┌─────────────────┐       ┌─────────────────┐
│ a = 0           │       │ a = 0           │
│                 │       │                 │
│ return a ───────┼───┐   │ return a ───────┼───┐
│   ↓             │   │   │   ↓             │   │
│ 返回值 = 0      │   │   │ 返回值 = 0      │   │
│                 │   │   │                 │   │
│ defer a++       │   │   │ defer a++       │   │
│   ↓             │   │   │   ↓             │   │
│ a = 1           │   │   │ a = 1           │   │
│                 │   │   │ 返回值 = 1 ←────┘   │
│ 返回 0          │   │   │                 │   │
└─────────────────┘   │   │ 返回 1          │   │
                      │   └─────────────────┘   │
                      └─────────────────────────┘

💡 记忆技巧

返回值类型 defer 能修改返回值吗 原因
匿名 func f() int ❌ 不能 返回值是副本
命名 func f() (x int) ✅ 能 返回值是同一个变量

一句话:

命名返回值 = 同一个变量,defer 可以改;匿名返回值 = 快照,改了也没用。


🧪 验证小实验

go 复制代码
func f2_5() int {
    var a int = 10
    defer func() { a = 999 }()
    return a
}
// 结果:10(不是999)

func f3_5() (a int) {
    a = 10
    defer func() { a = 999 }()
    return a
}
// 结果:999

📌 总结

go 复制代码
f2() → 0   // 匿名返回值,defer 改的是局部变量
f3() → 1   // 命名返回值,defer 改的是返回值本身

面试高频变形题:

go 复制代码
func f4() (r int) {
    defer func() { r += 100 }()
    return 5
}
// 结果:105(先 r=5,defer 加100,返回105)

这是 Go 面试必考考点之一!

相关推荐
ZealSinger21 小时前
Go的DefaultClient超时为何总不生效
go·超时·context·net/http·defaultclient
我叫黑大帅1 天前
Go日志库工程选型与逃逸分析评测报告
后端·面试·go
福兮说1 天前
Go 优雅退出:Shutdown 之后,后台 goroutine 还在跑(四个实测的坑)
后端·go
jeffwang1 天前
不装分词插件,用 PostgreSQL 做中文商品搜索:bigram + 向量 + RRF,以及「单字搜不到」这个坑
搜索引擎·postgresql·go
云浪2 天前
通过 Goroutine 搞懂 Go 并发与常见的 9 个坑
后端·go
福兮说2 天前
singleflight 防缓存击穿,这四个坑它不会替你挡
go
我的div丢了肿么办2 天前
自定义类型和类型别名以及实例化结构体的5种方式
后端·go
小满zs2 天前
Go语言第十三章(互斥锁,读写锁)
后端·go
福兮说2 天前
errgroup 的六个坑:Wait 之后 ctx 已取消、SetLimit 嵌套死锁,以及另外四个
后端·go
福兮说2 天前
Go 解析邮件的三个坑:GBK 标题、QP 正文,以及 NextPart 偷偷帮你做的事
后端·go