go的作用域完全由{}代码块决定,变量在哪个{}中声明就仅在该{}及其嵌套{}内有效;if/for/switch自带独立作用域,:=与var作用域规则一致,包级变量属包作用域,闭包捕获的是变量本身而非拷贝。

Go里大括号{}到底管多大范围
Go的作用域完全由代码块(即{})决定,不是靠缩进,也不是靠函数名或文件名。一个var写在哪个{}里,就只在这个{}及其嵌套的{}里能用——出了这个花括号,编译器直接报undefined。
常见错误现象:undefined: x,尤其在if或for里声明变量后,想在外部用;或者在func里写了多个{},误以为变量能跨块访问。
- 函数体最外层
{}是函数作用域,里面声明的变量对整个函数可见 -
if、for、switch语句自带独立代码块,if x := 1; x > 0 { ... }里的x只在{}内有效 - 可以嵌套任意层数
{},每层都新建作用域,但不能“向上逃逸” - 包级变量(写在
func外的var)属于包作用域,首字母大小写决定是否导出
短变量声明:=和var在作用域上没区别,但容易误用
:=只是var的语法糖,它声明的变量作用域规则完全一致——关键在于它写在哪层{}里。但因为:=看起来像赋值,很多人会忽略它其实是在做声明,导致重复声明或作用域误判。
使用场景:适合函数内部快速声明局部变量;不适合在包级或条件分支里混用,尤其别在if里用:=声明一个同名变量,以为能“覆盖”外层变量——实际是新声明了一个同名但作用域更小的变量。
立即学习“go语言免费学习笔记(深入)”;
-
:=要求左边至少有一个新变量名,否则编译报错no new variables on left side of := - 在
if里写if x := f(); x > 0 { ... },这个x只在if的{}里活,别指望在else或外面用 - 想复用变量名又控制作用域?老实用
var x int显式声明,再赋值
变量生命周期 = 作用域结束时间,但内存释放不等于作用域结束
Go里变量的生命周期(lifetime)严格绑定作用域:函数返回时,函数内所有局部变量作用域结束;if块执行完,里面:=的变量就“不可见”了。但这不代表内存立刻回收——是否被GC清理,取决于有没有指针还引用它。
性能影响:如果在循环里反复创建大结构体或切片,即使每次作用域结束,只要没逃逸到堆上,栈空间会自动复用;但如果变量被返回、传入goroutine、或作为闭包捕获,就会逃逸,转为堆分配,增加GC压力。
- 用
go tool compile -gcflags="-m"可看变量是否逃逸 - 闭包捕获外部变量时,捕获的是变量本身,不是值拷贝——所以
for i := range s { go func() { println(i) }() }会全打印最后一个i值 - 想在循环里为每个迭代生成独立变量?用
for i := range s { i := i; go func() { println(i) }() },显式在每轮新建作用域
包级变量和init函数的作用域边界容易混淆
包级变量声明顺序和init函数执行顺序共同决定了它们的实际初始化时机,但作用域仍是包级——也就是说,只要在同一包内,无论在哪写var或init,都能互相引用(前提是不循环依赖)。
容易踩的坑:在init里调用尚未初始化的包级变量,或在多个init函数之间形成隐式依赖。Go按源码顺序执行init,但包级变量初始化优先于init函数运行。
- 包级变量初始化表达式中,可安全引用同一包内已声明(且顺序在前)的其他包级变量
-
init函数里访问的包级变量,一定是已完成初始化的;但不同文件的init顺序不确定,避免跨文件强依赖 - 不要在
init里启动长期运行的goroutine并捕获包级变量,容易造成意外持有或竞争
真正难处理的,是闭包捕获和goroutine中变量的“存活时间”与作用域的错位——这时候作用域结束了,但变量还在被别的地方用着,得靠你手动理清引用链。










