Go 语言精进之路——Go语言代码块与作用域理解

文章详细阐述了Go语言中代码块的概念,包括显式和隐式代码块,以及它们在if、for、switch等控制语句中的应用。通过示例解释了作用域规则,特别是变量在不同代码块内的生命周期和可见性,强调理解这些规则对于避免编程错误和提高代码可读性的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

如果不深入理解 Go 语言的代码块作用域,程序将产生我们无法理解的行为,比如说在循环中创建 goroutine func, 为什么需要传递参数至 goroutine 内部,否则所有的 func 使用的变量参数都是循环的最后一个值。
看下边这个 demo, 就需要深入理解 Go 语言代码块的作用域才能理直气壮的一口答对:

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

有两个答案选项:
A:1 2 3
B:无法通过编译
思考三秒钟🤔-----------------
正确答案是 A,你答对了吗,接下来将围绕上述例子来理解一下Go代码块(code block)和作用域(scope)规则,理解这些规则将有助于我们编写出正确且可读性高的代码。

代码块与作用域简介

Go语言中的代码块是包裹在一对大括号内部的声明和语句,且代码块支持嵌套。如果一对大括号之间没有任何语句,那么称这个代码块为空代码块。代码块是代码执行流流转的基本单元,代码执行流总是从一个代码块跳到另一个代码块。
显示代码块

  • 由大括号包裹,比如函数体、for 循环的循环体、if 语句的某个分支等。
    image.png

隐式代码块

  • 宇宙代码块: 所有Go源码都在该隐式代码块中,就相当于所有Go代码的最外层都存在一对大括号。
  • 包代码块: 每个包都有一个包代码块,其中放置着该包的所有Go源码。
  • 文件代码块:每个文件都有一个文件代码块,其中包含着该文件中的所有Go源码。
  • 每个if、for和switch语句均被视为位于其自己的隐式代码块中。
  • switch或select语句中的每个子句都被视为一个隐式代码块。

Go标识符的作用域是基于代码块定义的,作用域规则描述了标识符在哪些代码块中是有效的。下面是标识符作用域规则。
在函数内部声明的类型标识符的作用域范围始于类型定义中的标识符,止于其最里面的那个包含块的末尾:
image.png

if 条件控制语句的代码块

  1. 单 if 型

    同时包含一个隐式代码块和一个显式代码块:
    image.png

    func Foo() {
        if a := 1; true {
            fmt.Println(a)
        }
    }
    
    // 等价变换为
    func Foo() {
        {
            a := 1
            if true {
            	fmt.Println(a)
        	}
        }
    }
    
  2. if {} else {} 型

    包含三个代码块,单 if 的两个代码块,else 的显式代码块:
    image.png

    func Foo() {
        if a,b := 1, 2; false {
            fmt.Println(a)
        } else {
            fmt.Println(b)
        }
    }
    
    // 等价变换为
    func Foo() {
        {
            a, b := 1, 2
            if false {
                fmt.Println(a)
            } else {
            	fmt.Println(b)
        	}
        }
    }
    
  3. if {} else if {} else {} 型image.png**对上边的伪代码进行变换:
    image.png
    此时就可以对一开始的答案进行解释了:

    func main() {
        if a := 1; false {
        } else if b := 2; false {
        } else if c := 3; false {
        } else {
            println(a, b, c)
        }
    }
    
    // 进行等价变换后
    func main() {
        {
            a := 1
            if false {
            } else {
                {
                    b := 2
                    if false {
                    } else {
                        {
                            c := 3
                            if false {
                            } else {
                                println(a, b, c)
                            }
                        }
                    }
                }
            }
        }
    }
    

其他控制语句的代码块

  1. for 语句的代码块
    通用 for 控制语句等价变换:
    image.png

    for a, b := 1, 10; a < b; a++ {
        ...
    }
    
    // 等价转换为
    {
       	a, b := 1, 10
        for ; a < b; a++ {
        	...
    	}
    }
    

    for range 语句等价变换:
    image.png

    var sl = []int{1, 2, 3}
    for i, n := range sl {
        ...
    }
    
    // 等价变换为
    var sl = []int{1, 2, 3}
    {
        i, n := 0, 0
        for i, n := range sl {
        	...
    	}
    }
    
  2. switch-case 语句的代码块
    通用形式等价变换:
    image.png

    switch x, y := 1, 2; x + y {
    case 3:
        a := 1
        fmt.Println("case1: a = ", a)
        fallthrough
    case 10:
        a := 5
        fmt.Println("case2: a = ", a)
        fallthrough
    default:
        a := 7
        fmt.Println("default case: a = ", a)
    }
    
    
    // 等价变换为
    {
        x, y := 1, 2
        switch x + y {
        case 3:
            {
                a := 1
                fmt.Println("case1: a = ", a)
            }
            fallthrough
        case 10:
            {
                a := 5
                fmt.Println("case2: a = ", a)
            }
            fallthrough
        default:
            {
                a := 7
                fmt.Println("default case: a = ", a)
            }
        }
    }
    
  3. select-case 语句的代码块

    通用形式等价变换:
    image.png

    c1 := make(chan int)
    c2 := make(chan int, 1)
    c2 <- 11
    select {
    case c1 <- 1:
        fmt.Println("SendStmt case has been chosen")
    case i := <-c2:
        _ = i
        fmt.Println("RecvStmt case has been chosen")
    default:
        fmt.Println("default case has been chosen")
    }
    
    // 等价变换为 (伪代码)
    c1 := make(chan int)
    c2 := make(chan int, 1)
    c2 <- 11
    select {
    case c1 <- 1:
        {
            fmt.Println("SendStmt case has been chosen")
        }
    case "如果该case 被选择":
        {
            i := <-c2:
            _ = i
            fmt.Println("RecvStmt case has been chosen")
        }
    default:
        {
            fmt.Println("default case has been chosen")
        }
    }
    

总结

各类隐式代码块的规则才是理解Go代码块和作用域的规则的“金钥匙”,尤其是那些对于程序执行流有重大影响的控制语句的隐式代码块规则。
理解Go代码块和作用域的规则将有助于我们快速解决类似“变量未定义”的错误和上一层变量被内层同名变量遮蔽(shadow)的问题,同时对于正确理解Go程序的执行流也大有裨益。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一切如来心秘密

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值