Skip to content

作用域

作者: ryan 发布于: 2025/7/11 更新于: 2025/7/11 字数: 0 字 阅读: 0 分钟

作用域是指一个标识符(变量、常量、函数等)的可见范围,即标识符在代码中可以被访问的区域。

作用域的类型

  • 全局作用域:标识符在整个程序中可见,通常定义在包的顶层代码中。
  • 局部作用域:标识符在某个封闭空间内可见,例如函数内部。

作用域的分类

宇宙块

宇宙块(Universe Block):宇宙块,意思就是全局块,不过是语言内建的。预定义的标识符就在这个全局环境中,因此bool、int、

nil、true、false、iota、append等标识符全局可见,随处可用。

语句块作用域

if、for、switch等语句中使用短格式定义的变量,可以认为就是该语句块的变量,作用域仅在该语句块中。

go
package main  
  
import "fmt"  
  
func test() {  
    a := 100    //局部变量,本地变量  
    var b = 200 //局部变量  
    fmt.Println(a, b)  
}  
  
func main() {  
    fmt.Println(a, b) // 编译错误!无法访问 test 的局部变量  
    test()  
}

for 循环的作用域

go
s := []int{1, 3, 5}
for i, v := range s {      // i, v 的作用域仅限 for 块内
    fmt.Println(i, v)      // ✅ 内部可见
}
fmt.Println(i, v)          // ❌ 编译错误:i, v 超出作用域[1][6]

迭代变量 iv 在循环结束时销毁,外部访问会导致编译错误。

if 语句的作用域

go
if f, err := os.Open("o:/t.txt"); err != nil { 
    fmt.Println(f, err)  // ✅ 内部可见
}
fmt.Println(f, err)       // ❌ 编译错误:f, err 超出作用域[2][10]

初始化变量(如 ferr)的作用域覆盖整个 if-else 块(包括所有分支)外部访问这些变量会触发编译错误。

switch/select 语句的作用域

go
x := 1
switch y := x + 1; y {    // y 的作用域覆盖所有 case
case 2:
    fmt.Println(y)        // ✅ 子句中可见
default:
    fmt.Println(y)        // ✅ 子句中可见
}
fmt.Println(y)            // ❌ 编译错误:y 超出作用域[6][7]

switch 的初始化变量(如 y)在 case 子句内有效,但外部不可访问。

显式的块作用域

在任何一个大括号中定义的标识符,其作用域只能在这对大括号中。

go
{
    const a = 100          // 常量仅在此块内有效
    var b = "hello"        // 变量仅在此块内有效
    c := true              // 短变量声明仅在此块内有效
    fmt.Println(a, b, c)   // ✅ 块内可见
}
fmt.Println(a, b, c)       // ❌ 编译错误:标识符未定义[1][4][6]

包块

(Package Block)包内全局可见的标识符,定义在包的顶层代码中。

每一个package包含该包所有源文件,形成的作用域。有时在包中顶层代码定义标识符,也称为全局标识符。 所有包内定义全局标识符,包内可见。包的顶层代码中标识符首字母大写则导出,从而包外可见,使用时也要加上包名。例如 fmt.Prinf()

  • 小写首字母:包内可见,包外不可见。
  • 大写首字母:包外可导出,需通过包名访问(如 fmt.Println)。

函数作用域

go
package main

import "fmt"

// 包级作用域(整个包可见)
const a = 100  // 常量不可取地址 
var b = 200
var d = 400

func showB() int {
    return b  // 访问包级变量b 
}

func main() {
    // 1. 常量与变量遮蔽 - 包级 vs 局部
    fmt.Println(1, a)        // 输出包级常量a=100 
    var a = 500             // 局部变量遮蔽包级常量a 
    fmt.Println(2, a, &a)   // 输出局部变量a=500及其地址



    // 2. 全局变量修改与局部遮蔽
    fmt.Println(3, b, &b)  // 包级变量b=200
    b = 600                 // 修改包级变量值
    fmt.Println(3.1, b, &b) // b=600(地址不变)
    b := 601                // 局部变量遮蔽包级b [6][9]
    
    fmt.Println(3.2, b, &b) // 输出局部b=601(地址变了->新地址)
    fmt.Println(3.3, showB()) // 显示包级b=600 之前修改了包级变量值200->600(不受局部遮蔽影响)



    // 3. 嵌套代码块作用域
    {
        const j = 'A'      // 块级常量(仅当前块可见)
        b := 800          // 遮蔽外层的局部b(main中的b=601)
        fmt.Println(4, a, b) // a=500(main局部), b=800
        {
            x := 900       // 内层块变量(仅当前块可见)
            fmt.Println(4.1, x) // 输出900
        }
        // fmt.Println(x) // 编译错误:x超出作用域 
    }
    // fmt.Println(j) // 编译错误:j超出作用域
    fmt.Println(4.4, b) // 恢复main的局部b=601
    
    // 4. 控制语句作用域
    for i, v := range []int{1, 3, 5} {
        fmt.Println(i, v) // i,v仅在循环内可见 [6][10]
    }
    // fmt.Println(i, v) // 编译错误
}
  • 内层作用域同名标识符遮蔽外层(如局部a=500遮蔽包级常量a=100
  • 局部b:=601和块级b:=800均遮蔽包级变量b,但不修改其原始值。


变量查找顺序:内层 → 外层(从最近作用域向外穿透)

作用域边界

  • {}代码块:内部变量(如x=900)外部不可访问
  • for循环:i,v仅在循环内部可见
  • 常量保护:包级常量a不可取地址(&a非法)

作用域的规则

  1. 向内穿透:内层作用域可访问外层变量(如函数内访问包级变量)。
  2. 就近优先:内层定义同名标识符时,优先使用最近的定义(遮蔽外层)。
  3. 对外不可见:外层无法访问内层作用域中的标识符(如函数外无法访问函数内变量)。