go变量作用域

83 次阅读

Go 语言变量作用域详解

一、作用域基础概念

变量作用域指变量可被访问、使用的代码范围,超出范围则变量失效、无法读取。
Go 根据定义位置分为四类作用域:

  1. 包级作用域(全局变量)
  2. 函数级作用域(局部变量)
  3. 代码块作用域(if/for/switch 内部)
  4. 函数参数作用域

核心规则:内层作用域可访问外层变量;内层定义同名变量会屏蔽外层变量(变量遮蔽)。

二、包级作用域(全局变量)

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未定义
}

短变量 := 只能用于函数内,不能定义包级变量。

四、代码块作用域(块级局部)

{} 包裹的代码块形成独立作用域,包含场景:
ifforswitch、单独 {} 代码块

示例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 从上到下、由内向外查找:

  1. 当前最小代码块内;
  2. 外层函数/外层代码块;
  3. 包级全局变量;
  4. 找不到则编译报错。

八、不同作用域变量生命周期

  1. 包级变量:程序启动初始化,程序退出销毁;
  2. 函数/块局部变量:进入代码块分配内存,执行完毕自动释放;
  3. 特殊:局部变量被闭包捕获,生命周期会延长,函数结束仍存在。

闭包延长变量生命周期示例

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被闭包持有,未销毁
}

九、作用域使用规范

  1. 尽量缩小变量作用域,在哪使用就在哪定义,减少全局变量;
  2. 避免多层作用域同名变量,防止变量遮蔽;
  3. 全局变量仅存放通用配置、常量,业务数据优先局部;
  4. 循环、if 的临时变量放在块内,不污染外层代码。