Go 语言
声明、类型、语句与控制结构
代码块与作用域

代码块与作用域

我们先看一下 Go quiz, 请问下面的代码会输出什么?

func main() {
    if a := 1; false {
    } else if b := 2; false {
    } else if c := 3; false {
    } else {
        println(a, b, c)
    }
}

答案是:1 2 3。为什么呢?只有深入了解了 Go 的代码块和作用域,才能理解这个问题。

什么是代码块

代码块是由大括号 {} 包裹的一段代码。在 Go 中,代码块可以包含一个或多个语句,也可以包含其他代码块。代码块是一个独立的作用域,代码块中定义的变量只在代码块内部有效。

如果一对大括号之间没有任何语句,那么称这个代码块为空代码块。空代码块在 Go 中是合法的,通常用于占位。

代码块是代码执行流流转的基本单元,代码执行流总是从一个代码块跳到另外一个代码块。

Go 代码块有两类,一类是在代码中直观可见的由一堆大括号包裹的显式代码块,另一类是没有大括号包裹的隐式代码块。

func Foo() {
    // 显式代码块
    {
        // 显式代码块
    }
 
    for {
        // 显示代码块
    }
 
    if true {
        // 显式代码块
    }
}

Go 规范定义了如下几种隐式代码块。

  • 宇宙(Universe)代码块: 所有 Go 源码都在该隐式代码块中,相对于所有 Go 代码的最外层都存在一对大括号。
  • 包代码块: 每个包都有一个包代码块,包代码块包含了包内所有的全局声明。
  • 文件代码块: 每个文件都有一个文件代码块。
  • 每个 if 、for 和 switch 语句都有一个隐式代码块。
  • switch 和 select 语句中的每个 case 语句都有一个隐式代码块。

什么是作用域

作用域是程序中变量的有效范围。在 Go 中,作用域是由代码块决定的。变量的作用域是指在程序中可以访问该变量的范围。

下面是标识符作用域的一些规则。

  • 预定义标识符:make、len、cap、new、append、copy、close、delete、complex、real、imag、panic、recover、print、println。
  • 顶层声明的标识符:包级别的变量、常量、类型、函数、方法、接口等。
  • Go 源文件中导入的包名称的作用域范围是文件代码块。
  • 方法接收器(recover)、函数参数、或返回值变量对应的标识符,作用域范围是函数代码块。
  • 在函数内部声明的局部变量的作用域范围是函数代码块。
func Foo() {
    // 局部变量 a 的作用域范围是函数代码块
    var a int
    type bar struct {} // 类型标识符作用域始于此
    {
        // 局部变量 b 的作用域范围是代码块
        var b int
    }
    // 类型标识符作用域止于此
}

if 语句的代码块

我们接下来看看三种类型 if 条件语句的代码块情况。

单 if

func Foo() {
    if a := 1; true {
        println(a)
    }
}
 
// 等价于
func Foo() {
    {
        // 隐式代码块
        a := 1
        if true { // 显式代码块
            println(a)
        }
    }
}

if else

func Foo() {
    if a, b := 1, 2; true {
        println(a)
    } else {
        println(b)
    }
}
 
// 等价于
func Foo() {
    {
        // 隐式代码块
        a, b := 1, 2
        if true { // 显式代码块
            println(a)
        } else { // 显式代码块
            println(b)
        }
    }
}

if else if else

func Foo() {
    if a := 1; false {
    } else if b := 2; false {
    } else if c := 3; false {
    } else {
        println(a, b, c)
    }
}
 
// 等价于
func Foo() {
    {
        // 隐式代码块
        a := 1
        if false { // 显式代码块
            {
                // 隐式代码块
                b := 2
                if false { // 显式代码块
                    {
                        // 隐式代码块
                        c := 3
                        if false { // 显式代码块
                        } else { // 显式代码块
                            println(a, b, c)
                        }
                    }
                }
            }
        }
    }
}

for 语句的代码块

for 有两种主要的使用形式,一种是 for init; condition; post {},另一种是 for range {}

for init; condition; post {} 的作用域:

func Foo() {
    for i := 0; i < 3; i++ {
        println(i)
    }
}
 
// 等价于
func Foo() {
    {
        // 隐式代码块
        i := 0
        for i < 3 { // 显式代码块
            println(i)
            i++
        }
    }
}

for range {} 语句的作用域:

func Foo() {
    for i, v := range []int{1, 2, 3} {
        println(i, v)
    }
}
 
// 等价于
func Foo() {
    {
        // 隐式代码块
        a := []int{1, 2, 3}
        i, v := 0, a[0]
        for i, v = range a { // 显式代码块
            println(i, v)
        }
    }
}

switch 语句的代码块

switch-case 语句的通用形式:

switch sExpr {
case expr1:
    some instructions
case expr2:
    some other instructions
default:
    other code
}

switch-case 语句的作用域:

func Foo() {
    switch a := 1; a {
    case 1:
        println(a)
    }
}
 
// 等价于
func Foo() {
    {
        // 隐式代码块
        a := 1
        switch a { // 显式代码块
        case 1: // 显式代码块
            println(a)
        }
    }
}

select 语句的代码块

select-case 语句的通用形式:

select {
case SendStmt:
    some instructions
case RecvStmt:
    some other instructions
default:
    other code
}

select-case 语句的作用域:

func Foo() {
    c1 := make(chan int)
    c2 := make(chan int, 1)
    c2 <- 11
 
    select {
    case c1 <- 1:
        println("c1")
    case i := <-c2:
        println("c2:", i)
    default:
        println("default")
    }
}
 
// 等价于
func Foo() {
    {
        // 隐式代码块
        c1 := make(chan int)
        c2 := make(chan int, 1)
        c2 <- 11
        select { // 显式代码块
        case c1 <- 1: // 显式代码块
            {
                println("c1")
            }
        case "如果该 case 被选择": // 显式代码块
            {
                i := <-c2
                println("c2:", i)
            }
        default: // 显式代码块
            {
                println("default")
            }
        }
    }
}