对一段有关Go Code Block和变量作用域的代码的简要分析

bigwhite · · 3099 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

近期,Go team的David CrawShawtwitter上贴出了一段代码,如下:

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

David CrawShaw想表达的意图是gopher们很少在”else if”后面的simple statement中使用“短变量声明”形式,而这段代码是个例外。我们看到b、c两个变量都是在else if 的simple statement中使用短变量声明形式定义的。

我个人看到这段代码后,第一反应是:这段代码能编译运行吗?else语句中的“println(a, b, c)”是否会被compiler报出:undefined b, c的错误呢?不知道是否有其他的gopher们与我有同样的反应:)。无论怎样,既然有了疑问,我们就应该把它分析清楚。

一. Go代码块和作用域简介

根据Go语言的规范,我们知道Go的标识符作用域是基于代码块(code block)的。代码块就是包裹在一对大括号内部的声明和语句,并且是可嵌套的。我们在代码中直观可见的显式的(explicit)code block有很多,比如:函数的函数体、for循环的循环体等:

func Foo() {
    // here:显式的(explict block)代码块,包裹在函数的函数体内
   ... ...

    for {
            // here: 显式的(explict block)代码块,包裹在for循环体内
            // 该代码块就嵌套在函数体这个代码块的内部
        ... ...
    }
}

但除了显式explict的code block,Go语言中还有几种隐式的(implicit)代码块,它们都是什么呢?这里摘录下go spec原文(不翻译了):

1. The universe block encompasses all Go source text.
2. Each package has a package block containing all Go source text for that package.
3. Each file has a file block containing all Go source text in that file.
4. Each "if", "for", and "switch" statement is considered to be in its own implicit block.
5. Each clause in a "switch" or "select" statement acts as an implicit block.

我们看到if语句会引入一个隐式的code block,这为我们后续的分析奠定了基础。

二. if语句的code block

那么if语句的code block详细情况如何呢?我们分门别类地简单看看:

1. if _ 型

我们使用最多的if语句类型就是单if型,即:

if simplestmt; expression {
    ... ...
}

在这种类型的if语句中,有两个code block:一个隐式的code block和一个显式的code block。我们把上面的形式代码做一个等价变化,并加上code block起始和结束点的标注,结果如下:

{ // implicit block begin
    simplestmt

    if expression { // explicit block begin
            ... ...
    } // explicit block end

} // implicit block end

我们看到if后面的”大括号对”引入的explict code block嵌套在if simplestmt所在的implicit code block内部,这也是为何simplestmt中用短声明形式定义的变量在explict block中可以使用的原因:

func main() {
    if a := 1; true {
        fmt.Println(a) // output: 1
    }
}

2. if _ else _ 型

我们再来看看if _ else _ 型

if simplestmt; expression {
    ... ...
} else {
    ... ...
}

分析逻辑同上,我们将上面的伪代码做一个等价变换,并作出code block起始结束点标注:

{ // implicit block begin
    simplestmt

    if expression { // explicit block1 begin
            ... ...
    }  else  { // explicit block1 end, explicit block2 start
            ... ...
    } //explicit block2 end

} // implicit block end

我们看到if _ else _ 型 有三个code block,除了单if型的两个block外,还由else引入一个explict code block(即上面代码中的explict block2)。

3. if _ else if _ else _ 型

最后我们来看看最为复杂的if _ else if _ else _ 型

if simplestmt1; expression1 {
    ... ...
} else if simplestmt2; expression2 {
    ... ...
}  else {
    ... ...
}

我们依旧将上面的伪代码做一个等价变换,并作出code block起始结束点标注,结果如下:

{ // implicit block1 begin
    simplestmt1

    if expression { // explicit block1 begin
            ... ...
    }  else  { // explicit block1 end, explicit block2 start

            {  // implicit block2 begin
                simplestmt2

                if expression2 { //  explicit block3 start

                } else { // explicit block3 end, explicit block4 start

                } // explicit block4 end

            } // implicit block2 end

    } //explicit block2 end

} // implicit block1 end

我们看到在该类型下,我们一共识别出两个implict block和四个explict block。

三. 对David CrawShaw贴出的那段代码的分析

有了第二节的基础,再来看David CrawShaw的这段代码:

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)
                        }
                    }
                }
            }
        }
    }
}

展开后的语句就很是一目了然了,不用说什么大家也会很清楚了。最重要的一点是原来代码中最后的那个else实际上是与最内层的else if配对的,而不是与最开始的if配对的,因此println(a, b, c)的时候,a, b, c三个变量都是已经声明定义了的(在外层的code block中)。

对于此类涉及code block或变量作用域的问题,还可以通过go vet -shadow工具来辨别,或通过go run执行后的出错信息来辨别,这里就不详细说明了。

四. 参考资料


有疑问加站长微信联系(非本文作者)

本文来自:Tony Bai

感谢作者:bigwhite

查看原文:对一段有关Go Code Block和变量作用域的代码的简要分析

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3099 次点击  ∙  5 赞  
加入收藏 微博
2 回复  |  直到 2022-07-26 09:35:40
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传