1.Go的词法作用域使用块(block),我们需要先理解块的概念:
块指的是一系列的语句(语句为空的也是可以的)。块可以内嵌,并且被大括号包含:
package main
import (
"fmt"
)
func main() {
{ // 外部块开始
a := 1
fmt.Println(a)
{ //内部块开始
b := 2
fmt.Println(b)
} // 内部块结束
} // 外部块结束
}
除了显式的块之外,还有隐式的块:
- universe块包含所有的Go源文本
- package块包含包中的所源文本(包可以在一个文件夹中,分布在多个文件中存放)
- 每个文件都有一个file块,它包含该文件中所有的Go源文本
- if、for、switch可以看做是在他们自己的隐式的块中
- “switch”或“select”语句中的每个子句都充当隐式块。
for i := 1 ; i < 10 ; i ++ {
fmt.Println("current num is ",i)
}
变量i在初始化(init)语句中声明,它是在条件(condation)语句、函数体、post语句中可见的,如果在for语句之后使用变量i的话,你会的到编译异常。
if i := 0; i >= 0 {
fmt.Println(i)
}
我们声明了变量i,他可以被条件表达式所使用,当条件表达式为true,那么就执行嵌套的块,否则就执行else子句
switch i := 2; i * 4 {
case 8:
j := 0
fmt.Println(i, j)
default:
// "j" is undefined here
fmt.Println(“default”)
}
// "j" is undefined here
switch和if语句一样
根据语法,switch的每一个字句也是一个块,根据定义,我们需要为其添加两边的大括号,但是这样太不美观了。
2.作用域
我们通过声明将标识符绑定到变量、常量、包、类型、函数以及标签。
程序中的标识符必须声明,并且在相同的块中不可以有相同的声明,也没有标识符可以在package和file块中同时都声明
空白标识符可以像声明的任何其他标识符一样使用,但它不引入绑定,因此没有声明。在package块中,标识符init可能只用于初始化函数声明,和空白标识符一样,它不引入新的绑定。
声明的词法块决定了它的作用域,这个作用域可大可小。
在universe block中预声明了一些内建的类型,函数,还有常量,下面是总的:
Types:
bool byte complex64 complex128 error float32 float64
int int8 int16 int32 int64 rune string
uint uint8 uint16 uint32 uint64 uintptr
Constants:
true false iota
Zero value:
nil
Functions:
append cap close complex copy delete imag len
make new panic print println real recover
在任何函数之外的声明,也成为package level(包级),他可以被同包的任何文件所访问
而在文件中导入的包,他只能被当前文件访问,而不能被其他文件所访问(其他文件也不导入的前提下),这也被成为file level(文件级)
当编译器遇见对一个名字的引用时候,他会查找你对其的声明,从词法块开始一直到universe块为止,如果编译器没有发现声明,他会报出“undeclared name”错误。如果该名称有两个声明,分别处于不同的块中,那么会有限取内部块的声明,这也就是说内部的声明shadow或者hide了外部的声明,使其不可见。
注意:
词法作用域(lexical scope) = 静态作用域(static scope)
动态作用域(dynamically scope)
对于控制流标签,如break,continye以及goto语句,他们作用域是整个封闭的函数
下面做两种作用域的对比
//在动态作用域的语言中
func f(){
x := 1
g()
}
func g(){
fmt.Println(x) //是可以的。
}
//静态作用域
func f(){
x := 1
g()
}
func g(){
fmt.Println(x) //无法访问
}
package main
import “fmt”
func main() {
{
v := 1
{
fmt.Println(v)
}
fmt.Println(v)
}
// “undefined: v” compilation error
// fmt.Println(v)
}
对于有编程经验的工程师而言,完全可以理解
最后调用fmt.Println被注释掉,因为它会导致编译错误。很快就会澄清为什么会这样。短变量v在包含其声明的右花括号后即超出了其作用域
值得一提的是,为变量分配新值并不会影响作用域(即可见性):
v := 1 //声明
{
v = 2 // 赋值
fmt.Println(v)
}
fmt.Println(v) //依然可以访问
但是下面的情景即不一样了:
v := 1 // 短变量声明
{
v := 2 // 短变量声明
fmt.Println(v)
}
fmt.Println(v)
作用域与标识符的声明息息相关