Go进阶之代码块和作用域

18 阅读6分钟

先看一段代码.

package main

import (
    "fmt"
    "gomodule/data"
    _ "gomodule/pubsub"
)

func main() {

    if a := 1; false {

    } else if b := 2; false {

    } else if c := 3; false {

    } else {
       fmt.Println(a, b, c)
    }

}

有两个选项.

A:1 2 3.

B:无法编译.

第一眼看到的时候.会感觉没办法编译.会觉着编译器报出 "undefined b"的错误.执

行一下会得到如下结果.

1.Go代码块与作用域简介:

Go语言中代码块是包裹在一对大括号内部的声明和语句.且代码块支持嵌套.如果大括

号之间没有任何语句.称这个代码块为空代码块.代码块是代码执行流流转的基本单元.

代码执行流总是从一个代码块跳到另一个代码块.

2.Go语言中代码块类型:

在代码中直观可见的由一大堆大括号包裹的显示代码块.比如函数体 for循环体 if语

句的某个分支等:示例代码如下:

func Foo() {
    //包裹在函数体内的显示代码块.
    //...

    for {
       //包裹在for循环内的显示代码块
       //该代码块也是嵌套在函数体显示代码块内部的代码块
       //...
    }

    if true {
       //包裹在if语句true分支内的代码块.
       //该代码块也是嵌套在函数体显示代码块内部的代码块
       //...
    }
}

没有大括号包裹的的隐式代码块(Go规范定义了如下几种隐式代码块):

1).宇宙代码块:所有Go源码都在隐式代码块中.相当于所有Go代码最外层都是一对大

括号.

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

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

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

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

3.Go作用域:

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

3.1预定义标识符:

make new cap len等的作用域范围是宇宙块.

3.2:顶层(任何函数之外)声明的常量 类型 变量或函数(但不是方法)对应的标识符作

用域是包代码块.比如包级变量 包级常量的标识符的作用域都是包代码块.

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

3.4:方法接收器 函数参数或返回值变量对应的标识符作用域范围是函数体(显示代码

块).虽然他们并没有被函数体的大括号显示包裹.

3.5:在函数内部声明的常量或变量对应的标识符的作用域范围始于常量或变量声明语

句的末尾.止于其最里面的哪个包含块的末尾.

3.6在函数内部声明的类型标识符的作用域范围始于类型定义中的标识符,止于其最里

面的那个包含块的末尾.

4.if条件语句代码块:

4.1单if型:

if SimpleStmt;Expression{
    ...
}

根据规则得知if本身就存在一个隐式代码块中.所以会有两个代码块.一个隐式一个显

示.等价变化如下:

{//隐式代码块开始
 SimpleStmt
if Expression{//显示代码块开始
    ...
}//显示代码块结束
}//隐式代码块结束

可以看到if后面的代码块嵌套在了SimpleStmt隐式代码块中.这也就是为什么变量可

以代码块内使用.示例代码如下:


func FooIf() {
    if a := 1; true {
       fmt.Println(a)
    }
}

func FooIf() {
    {
       a := 1
       if true {
          fmt.Println(a)
       }
    }
}

在等价变化后.根据上面的作用域规则得知if显示代码块中使用变量是合法的.

4.2if{}else{}型:

if SimpleStmt;Expression{
...
}else{

}

对上吗伪代码进行等价替换如下:

{//隐式代码块开始
SimpleStmt
if Expression{//1显示代码块开始
...
//显示代码块1结束
}else{//2显示代码块开始
...
}//显示代码块1结束
}//隐式代码块结束

可以看到if{}else{}型控制语句有三个代码块.除了单if的两个代码块还有一个else引

入的显示代码块.示例如下:

func FooIf() {
    if a,b := 1,2; true {
       fmt.Println(a)
    }else {
       fmt.Println(b)
    }
}

func FooIf() {
    {
       a,b := 1,2
       if false {
          fmt.Println(a)
       }else {
          fmt.Println(b)
       }
    }
}

4.3if{}else if{}else{]型:

伪代码如下:

等价替换如下:

{//隐式代码块1开始
SimpleStmt1
if Expression1{//显示代码块1开始
...
//显示代码块1结束
}else{//显示代码块2开始
...
{//隐式代码块2开始
SimpleStmt2
if Expression2{//显示代码块3开始
...
}else{//显示代码块3结束.显示代码块4开始
    ...
}//显示代码块4结束
}//隐式代码块2结束
}//显示代码块2结束
}//隐式代码块1结束

示例如下:

func main() {

    if a := 1; false {

    } else if b := 2; false {

    } else if c := 3; false {

    } else {
       fmt.Println(a, b, c)
    }

}

func MainIf() {
    {
       a := 1
       if false {

       } else {
          {
             b := 2
             if false {

             } else {
                {
                   c := 3
                   if false {

                   } else {
                      fmt.Println(a, b, c)
                   }
                }
             }
          }
       }
    }
}

上面的题目就迎刃而解了.

5.其他代码块规则简介:

5.1for语句代码块:

通用for控制语句:

for InitStmt;Condition;PostStmt{
    ...
}

for range语句:

for IndentifierList := range Expressiom {
    ...
}

for语句转换形式如下:

{//隐式代码块开始
InitStmt
for Condition;PostStmt{
    //for显示代码块
    ...
}
}//隐式代码块结束

通用for循环示例如下:

for a, b := 1, 10; a < b; a++ {
    ...
}

{
    a, b := 1, 10
    for ; a < b; a++ {
       ...
    }
}

for-range等价转换如下:

{//隐式代码块开始
IndentifierList=IndentifierValueList
for IndentifierList= range Expressiom{
//for显示代码块
...
}

for-range示例如下:

var s1 = []int{1, 2, 3}
for i, n := range s1 {
    ...
}

var s1 = []int{1, 2, 3}
{
    i, n := 0, 0

    for i, n := range s1 {
       ...
    }

}

5.2switch-case语句代码块:

switch-case语句通用格式:

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

隐式代码规则:

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

示例:

switch x, y := 1, 2; x + y {
case 3:
    a := 1
    fmt.Println(a)
    fallthrough
case 10:
    a := 5
    fmt.Println(a)
    fallthrough
default:
    a := 7
    fmt.Println(a)
}

{
    x, y := 1, 2
    switch x + y {
    case 3:
       {
          a := 1
          fmt.Println(a)
       }
       fallthrough
    case 10:
       {
          a := 5
          fmt.Println(a)
       }
       fallthrough
    default:
       {
          a := 7
          fmt.Println(a)
       }
    }

}

可以看到不同case语句隐式代码块中的变量a都是独立的互不影响.

5.3select-case代码块:

select-case可以在case子句中通过短变量声明定义新变量.但该变量依然被纳入隐

式代码块中.

通用形式:

select {
case SendStmt:
...
case RecvStmt:
...
default:
...
}

隐式代码规则:

select{//显式代码块开始.
case SendStmt:
{//隐式代码块1开始
...
}//隐式代码块1结束
case RecvStmt:
{//隐式代码块2开始
...
}//隐式代码块2结束
default:
{//隐式代码块3开始
...
}//隐式代码块3结束
}//显示代码块结束

示例:

	c1 := make(chan int)
	c2 := make(chan int, 2)
	c2 <- 11
	select {
	case c1 <- 1:
		fmt.Println("c1")
	case i := <-c2:
		fmt.Println("c2")
	default:
		fmt.Println("default")
	}

	c1 := make(chan int)
	c2 := make(chan int, 2)
	c2 <- 11

	select {
	case c1 <- 1:
		{
			fmt.Println("c1")
		}
	case "如果被选择":
		{
			fmt.Println("c2")
		}
	default:
		{
			fmt.Println("default")
		}
	}
}

因为case触发条件特殊.所以用文字代替了.

檐下行人匆匆过.演尽世间分与合.三四灰发生鬓侧.醉罢挥毫弄墨.




如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路