
Go 采用词法作用域,内层变量声明会遮蔽外层同名变量;但函数调用的实参在内层变量声明前即完成求值,因此 repeat(list) 中的 list 引用的是外层变量。
Go 采用词法作用域,内层变量声明会遮蔽外层同名变量;但函数调用的实参在内层变量声明前即完成求值,因此 `repeat(list)` 中的 `list` 引用的是外层变量。
在 Go 中,变量遮蔽(shadowing)是一个常见但易被误解的语言特性。它并非“覆盖”或“修改”外层变量,而是在新作用域中重新声明一个同名变量,从而使得该作用域内对标识符的引用优先绑定到内层变量。关键在于:变量声明与表达式求值存在明确的时序关系。
根据 Go 语言规范关于作用域和声明的规定,变量的作用域由其声明所在的代码块决定;而函数调用的求值顺序严格遵循:先完全求值所有实参表达式,再执行函数体,最后才进行调用语句所在作用域内的新变量声明与赋值。
以问题中的代码为例:
list := []string{"a", "b", "c"}
for {
list := repeat(list) // ← 注意:此处的 list 是外层变量!
}虽然 list := repeat(list) 看似“一边声明一边使用”,但 Go 的语义是:
- 先求值右侧表达式 repeat(list) → 此时 list 尚未被内层重新声明,解析为外层变量;
- 调用 repeat 函数,传入外层 list 的当前值(如 []string{"a","b","c"});
- 待 repeat 返回后,才将返回值赋给新声明的内层 list 变量(该变量仅在 for 块内可见)。
因此,内层 list 的声明不会影响实参求值阶段的标识符解析——这是词法作用域与求值顺序共同保证的确定性行为。
⚠️ 注意事项:
- 遮蔽本身合法但易引发逻辑混淆,尤其在循环、闭包或错误处理中误用 err := ... 时可能导致外层 err 未被更新;
- go vet 工具可检测部分可疑遮蔽(如 -shadow 检查),建议在 CI 中启用;
- 若需复用外层变量名并修改其值,应使用赋值 list = repeat(list) 而非短变量声明 list := repeat(list)。
总结:Go 的遮蔽是静态作用域规则的自然结果,而非运行时动态绑定。理解“求值优先于声明”的原则,是写出清晰、可维护 Go 代码的关键基础。