代码块与作用域
我们先看一下 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")
}
}
}
}