go变量作用域
83 次阅读
Go 语言变量作用域详解
一、作用域基础概念
变量作用域指变量可被访问、使用的代码范围,超出范围则变量失效、无法读取。
Go 根据定义位置分为四类作用域:
- 包级作用域(全局变量)
- 函数级作用域(局部变量)
- 代码块作用域(
if/for/switch内部) - 函数参数作用域
核心规则:内层作用域可访问外层变量;内层定义同名变量会屏蔽外层变量(变量遮蔽)。
二、包级作用域(全局变量)
1. 定义位置
函数外部、包内直接定义,整个包所有函数均可访问。
package main
import "fmt"
// 包级变量,全局生效
var msg string = "包级变量"
func test1() {
fmt.Println(msg) // 可访问
}
func test2() {
fmt.Println(msg) // 可访问
}
func main() {
test1()
test2()
}
2. 导出规则
- 首字母大写:其他包也能访问;
- 首字母小写:仅当前包内部可用。
package demo
// 其他包可访问
var PublicVar int = 100
// 仅本包可见
var privateVar int = 200
3. 执行顺序
包级变量先初始化,再执行 init() 函数,最后执行 main()。
三、函数级作用域
在函数内部定义,仅当前函数内有效,其他函数无法访问。
package main
import "fmt"
func test() {
// 函数局部变量
var num int = 10
fmt.Println(num)
}
func main() {
test()
// fmt.Println(num) // 编译报错:num未定义
}
短变量 := 只能用于函数内,不能定义包级变量。
四、代码块作用域(块级局部)
{} 包裹的代码块形成独立作用域,包含场景:
if、for、switch、单独 {} 代码块
示例1:if 块作用域
func main() {
if flag := true; flag {
fmt.Println(flag) // 块内可用
}
// fmt.Println(flag) // 报错,if块结束变量销毁
}
示例2:for 循环块作用域
func main() {
for i := 0; i < 3; i++ {
fmt.Println(i)
}
// fmt.Println(i) // 循环外无法访问i
}
示例3:自定义独立代码块
func main() {
{
name := "块内变量"
fmt.Println(name)
}
// fmt.Println(name) // 超出块,失效
}
五、函数参数作用域
函数形参作用域为整个函数体,等价函数局部变量。
func add(a, b int) int {
// a、b 仅add函数内可用
return a + b
}
func main() {
// fmt.Println(a) // 不存在
}
六、变量遮蔽(内层同名覆盖外层)
内层作用域定义和外层同名变量,内层优先使用,外层临时被隐藏。
package main
import "fmt"
var name = "全局张三"
func main() {
name := "局部李四" // 遮蔽全局变量
fmt.Println(name) // 输出:局部李四
if true {
name := "块内王五" // 遮蔽外层局部变量
fmt.Println(name) // 输出:块内王五
}
fmt.Println(name) // 输出:局部李四
}
开发不建议滥用同名变量,容易引发逻辑bug。
七、作用域查找顺序(就近原则)
使用变量时,Go 从上到下、由内向外查找:
- 当前最小代码块内;
- 外层函数/外层代码块;
- 包级全局变量;
- 找不到则编译报错。
八、不同作用域变量生命周期
- 包级变量:程序启动初始化,程序退出销毁;
- 函数/块局部变量:进入代码块分配内存,执行完毕自动释放;
- 特殊:局部变量被闭包捕获,生命周期会延长,函数结束仍存在。
闭包延长变量生命周期示例
func gen() func() int {
i := 0 // 函数局部变量
return func() int {
i++
return i
}
}
func main() {
f := gen()
fmt.Println(f()) // 1
fmt.Println(f()) // 2
fmt.Println(f()) // 3
// gen函数已执行完毕,但i被闭包持有,未销毁
}
九、作用域使用规范
- 尽量缩小变量作用域,在哪使用就在哪定义,减少全局变量;
- 避免多层作用域同名变量,防止变量遮蔽;
- 全局变量仅存放通用配置、常量,业务数据优先局部;
- 循环、if 的临时变量放在块内,不污染外层代码。