运行时

Written by with ♥ on in _

作用域

Go 语言使用词法作用域,对变量的查找遵循词法作用域的规则。

在程序中声明的各种变量、函数名都有着自己的作用域,并最终指向全局作用域

编译时,当编译器遇到一个变量名字引用,会首先从最内层的词法作用域向全局作用域查找,如果找不到,则会报出未声明的错误。内层作用域的名字会屏蔽外层作用域的相同名字。

语法块

要理解作用域需要先理解语法块(Blocks),语法块内部声明的变量无法被外部访问到。语法块在大多数情况下都由花括号包围 {},包围的块决定了内部声明名字的作用范围,在某些场景下不需要花括号,这种时候称为词法块

词法块分为:

  • 全局词法块,包含所有源代码
  • 包词法块,包含整个 Package 的所有源代码
  • 源文件词法块,包含文件的所有源代码
  • for、if、switch 词法块
  • switch、select 的分支也有独立的词法块
  • 显式书写词法块,花括号包含的语句 {}

Blocks 决定了作用域。

例如 intlentrue 等内置类型、函数、常量都是在全局作用域,即全局词法块,可以在程序中直接使用。

在一个包中声明的函数可以在包中的任一源文件里使用,即包词法块(包作用域)。

对于导入的包,只对于源文件级别的作用域,只能在当前文件中访问导入的包,当前包中的其他源文件无法访问。

package main

import "fmt"

func main() {
    { // 创造一个作用域(显示书写词法块)
        a := 1
        fmt.Println(a)
        { // 创造一个内部作用域
            b := 2
            fmt.Println(b)
        }
    }
}

常见词法块

// for 词法块创建属于自己的隐式作用域
// i 变量声明在 for 创建的隐式作用域中,外部无法访问
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// if 词法块创建属于自己的隐式作用域
// i 变量声明在 if 创建的隐式作用域中,外部无法访问
if i := 0; i >= 0 {
    fmt.Println(i)
}

// switch 词法块创建属于自己的隐式作用域
// case 分支也有自己的作用域
switch i := 2; i * 4 {
case 8:
    j := 0
    fmt.Println(i, j)
default:
    // 这里访问不到 j
    fmt.Println(“default”)
}

// select 词法块创建属于自己的隐式作用域
// case 分支也有自己的作用域
tick := time.Tick(100 * time.Millisecond)
for {
    select {
    case <-tick:
        i := 0
        fmt.Println(“tick”, i)
    default:
        // 这里访问不到 i
        fmt.Println(“sleep”)
        time.Sleep(30 * time.Millisecond)
    }
}

明白了词法块如何创建作用域,下面就可以非常简单的理解嵌套的作用域中名字的查找规则。

理解了 Blocks 就能够理解作用域的变量访问规则了。

package main

import "fmt"

func main() {
    { // 创建 main 函数内部的作用域
        v := 1
        { // 创建内部作用域
            v := 2
            fmt.Println(v)
        }
        fmt.Println(v)
    }
    // 访问不到 v
    // fmt.Println(v)
}

Out:

2
1