《Go语言精进之路》读书笔记 | 理解Go语言代码块与作用域

Posted COCOgsta

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Go语言精进之路》读书笔记 | 理解Go语言代码块与作用域相关的知识,希望对你有一定的参考价值。

书籍来源:《Go语言精进之路:从新手到高手的编程思想、方法和技巧》

一边学习一边整理读书笔记,并与大家分享,侵权即删,谢谢支持!

附上汇总贴:《Go语言精进之路》读书笔记 | 汇总_COCOgsta的博客-CSDN博客


理解Go代码块(code block)和作用域(scope)规则将有助于我们编写出正确且可读性高的代码。

18.1 Go代码块与作用域简介

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

Go语言中有两类代码块,一类是我们在代码中直观可见的由一堆大括号包裹的显式代码块,比如函数的函数体、for循环的循环体、if语句的某个分支等。

另一类则是没有大括号包裹的隐式代码块。Go规范定义了如下几种隐式代码块。

宇宙(Universe)代码块:所有Go源码都在该隐式代码块中,就相当于所有Go代码的最外层都存在一对大括号。

包代码块:每个包都有一个包代码块,其中放置着该包的所有Go源码。

文件代码块:每个文件都有一个文件代码块,其中包含着该文件中的所有Go源码。

每个if、for和switch语句均被视为位于其自己的隐式代码块中。

switch或select语句中的每个子句都被视为一个隐式代码块。

Go标识符的作用域是基于代码块定义的,作用域规则描述了标识符在哪些代码块中是有效的。下面是标识符作用域规则。

预定义标识符,make、new、cap、len等的作用域范围是宇宙块。

顶层(任何函数之外)声明的常量、类型、变量或函数(但不是方法)对应的标识符的作用域范围是包代码块。

Go源文件中导入的包名称的作用域范围是文件代码块。

方法接收器(receiver)、函数参数或返回值变量对应的标识符的作用域范围是函数体(显式代码块),虽然它们并没有被函数体的大括号所显式包裹。

在函数内部声明的常量或变量对应的标识符的作用域范围始于常量或变量声明语句的末尾,止于其最里面的那个包含块的末尾。

在函数内部声明的类型标识符的作用域范围始于类型定义中的标识符,止于其最里面的那个包含块的末尾。

18.2 if条件控制语句的代码块

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

  1. 单if型

最常见的if语句类型就是单if型,即

if SimpleStmt; Expression 
    ...

复制代码

根据代码块规则,if语句自身在一个隐式代码块中,因此单if类型的控制语句中有两个代码块:一个隐式代码块和一个显式代码块。我们对上面的代码进行一个等价变换,并加上代码块起始点和结束点的标注。

 // 隐式代码块开始
    SimpleStmt

    if Expression  // 显式代码块开始
            ...
     // 显式代码块结束

 // 隐式代码块结束
复制代码

这就是为何SimpleStmt中使用短变量声明形式定义的变量可以在if语句的显式代码块中使用。

  1. if else 型

再来看看if else 型控制语句的代码块的分布:

if Simplestmt; Expression 
    ...
 else 
    ...

复制代码

对上面的伪代码进行一个等价变换并给出代码块起始点和结束点的标注。

 // 隐式代码块开始
    SimpleStmt

    if Expression  // 显式代码块1开始
        ...
        // 显式代码块1结束
      else   // 显式代码块2开始
        ...
       // 显式代码块2结束

 // 隐式代码块结束
复制代码

if else 型控制语句有三个代码块,除了单if型的两个代码块外,还有一个由else引入的显式代码块。用个例子来说明一下:

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)
        
    

复制代码

在SimpleStmt中声明的变量,其作用域范围可以延伸到else后面的显式代码块中。

  1. if else if else 型

最后来看看最为复杂的if else if else 型:

if SimpleStmt1; Expression1 
    ...
 else if SimpleStmt2; Expression2 
    ...
 else 
    ...

复制代码

我们对上面的伪代码进行等价变换,并作出代码块起始点和结束点的标注,结果如下:

 // 隐式代码块1开始
    SimpleStmt1

    if Expression1  // 显式代码块1开始
        ...
     else  // 显式代码块1结束;显式代码块2开始
          // 隐式代码块2开始
            SimpleStmt2

            if Expression2  // 显式代码块3开始
                ...
             else  // 显式代码块3结束;显式代码块4开始
                ...
             // 显式代码块4结束
         // 隐式代码块2结束
     // 显式代码块2结束
 // 隐式代码块1结束
复制代码

在该类型下,一共识别出2个隐式代码块和4个显式代码块。

18.3 其他控制语句的代码块规则简介

在Go代码块规则中,多数隐式代码块规则都比较容易理解,除了最后两条:

每个if、for和switch语句均被视为位于其自己的隐式代码块中;

switch或select语句中的每个子句都被视为一个隐式代码块。

下面将对for、switch及select语句的代码块规则进行简要分析,思路依然是等价转换。

  1. for语句的代码块

Go语言的for控制语句有两种主要的使用形式,第一种是通用for控制语句:

for InitStmt; Condition; PostStmt 
    ...

复制代码

第二种是for range型语句:

for IndentifierList := range Expression 
    ...


// 或

for ExpressionList = range Expression 
   ...

复制代码

可以等价转换为下面的形式:

 // 隐式代码块开始
    InitStmt
    for Condition; PostStmt 
        // for显式代码块
        ...
    
 // 隐式代码块结束
复制代码

InitStmt中声明的变量的作用域涵盖了Condition、PostStmt和for的显式代码块。

for-range形式相对简单些:

for IndentifierList := range Expression 
    ...

复制代码

上述形式可等价转换为:

 // 隐式代码块开始
    IndentifierList := InitialValueList
    for IndentifierList = range Expression 
        // for的显式代码块
        ...
    
 // 隐式代码块结束
复制代码
  1. switch-case语句的代码块

下面是switch-case语句的通用形式:

switch SimpleStmt; Expression 
    case ExpressionList1:
        ...
    case ExpressionList2:
        ...
    default:
        ...

复制代码

根据switch-case隐式代码块的规则,我们可以将上述形式等价转换为下面的形式:

 // 隐式代码块1开始
    SimpleStmt
    switch Expression  // 显式代码块1开始
        case ExpressionList1:
         // 隐式代码块2开始
            ...
         // 隐式代码块2结束
        case ExpressionList2:
         // 隐式代码块3开始
            ...
         // 隐式代码块3结束
        default:
         // 隐式代码块4开始
            ...
         // 隐式代码块4结束
     // 显式代码块1结束
 // 隐式代码块1结束
复制代码

我们看到每个case语句都对应一个隐式代码块。

  1. select-case语句的代码块

和switch-case无法在case子句中声明变量不同的是,select-case可以在case字句中通过短变量声明定义新变量,但该变量依然被纳入case的隐式代码块中。

select-case语句的通用形式如下:

select 
    case SendStmt:
        ...
    case RecvStmt:
        ...
    default:
        ...

复制代码

根据隐式代码块的规则,我们可以将上述形式等价转换为下面的形式:

select  // 显式代码块开始
    case SendStmt:
     // 隐式代码块1开始
        ...
     // 隐式代码块1结束
    case RecvStmt:
     // 隐式代码块2开始,如果RecvStmt声明了新变量,那么该变量也应包含在隐式代码块2中
        ...
     // 隐式代码块2结束
    default:
     // 隐式代码块3开始
        ...
     // 隐式代码块3结束
 // 显式代码块结束
复制代码

以上是关于《Go语言精进之路》读书笔记 | 理解Go语言代码块与作用域的主要内容,如果未能解决你的问题,请参考以下文章

《Go语言精进之路》读书笔记 | 理解Go语言的设计哲学

《Go语言精进之路》读书笔记 | 理解Go语言的包导入

《Go语言精进之路》读书笔记 | 理解Go语言表达式的求值顺序

《Go语言精进之路》读书笔记 | 使用Go语言原生编码思维来写Go代码

《Go语言精进之路》读书笔记 | 使用Go语言原生编码思维来写Go代码

《Go语言精进之路》读书笔记 | 使用无类型常量简化代码