命名返回值可被defer修改,非命名返回值不能;因命名返回值是函数入口自动声明的局部变量,return先赋值再执行defer,最终返回其修改后值。
命名返回值可被 defer 修改,非命名返回值不能——这是唯一需要记住的核心规则。
命名返回值在 defer 中能改,是因为它本质是局部变量
函数签名中带名字的返回值(如
)会在函数入口自动声明为同名局部变量,初始为零值。return 语句实际是先给这个变量赋值,再触发 defer,最后返回该变量的当前值。
返回
,不是
最终返回
即使函数 panic,
仍执行,
被修改后,
也能读到新值
非命名返回值无法被 defer 影响,因为没有变量可写
像
这种写法,返回的是一个临时计算结果,不是变量。defer 函数里根本看不到这个值,更谈不上修改。
返回
——
改的是局部变量
,不是返回值本身
想让返回值变,必须把返回值“具名化”,否则
对它完全无感
defer 参数求值时机常被误判,导致“以为改了,其实没改”
语句里的参数在
那行执行时就快照了,不是
真正调用时才取值。这和闭包捕获变量的时间点一致。
go语言参考手册 中文CHM版
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。本文给大家带来Go参考手册,需要的可以来下载! Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。 Go 语言特色 简洁、快速、安全 并行、有趣、开源 内存管理、v数组安全、编译
下载
立即学习
“
go语言免费学习笔记(深入)
”;
打印的是
,不是
若想打印最终值,得用闭包延迟读取:
这个规则对命名返回值也适用:
里读
读到的是当时值,但写
是真写进那个变量
多个 defer 的 LIFO 顺序会影响返回值修改的最终效果
按后进先出执行,所以越晚注册的
,越早运行。这对返回值修改的叠加顺序很关键。
返回
(
)
顺序反过来:
就返回
(
)
别依赖这种叠加逻辑,容易绕晕;真正需要链式修改时,显式写清楚更安全
最易被忽略的一点:命名返回值的“可修改性”只在
内部生效,且仅限于该函数作用域。一旦函数返回,这个变量生命周期就结束——它不是全局或逃逸出去的引用。所以不要试图在
里启动 goroutine 去异步改它,那会读到不确定的值。
func f() (x int)func f() (x int) { defer func() { x = 10 }(); return 5 }105defer func() { x++ }(); return 56deferxrecover()func f() int { return 3 + 4 }func f() int { x := 42; defer func() { x = 99 }(); return x }42deferxdeferdeferdeferdeferi := 0; defer fmt.Println(i); i = 42042defer func() { fmt.Println(i) }()deferxx =deferdeferfunc f() (x int) { defer func() { x *= 2 }(); defer func() { x += 1 }(); return 3 }83 → +1=4 → ×2=8defer func() { x += 1 }(); defer func() { x *= 2 }()73 → ×2=6 → +1=7deferdefer