作用域
复习
- 符号表:记录名字与属性的账本
- 函数:把一段工作命名并封装
- 树:用父子关系表示层次结构
TL;DR
- 作用域是名字可见的程序范围
- 常见规则:内层可以遮蔽外层的同名名字
- 作用域通常由代码块或函数界定
- 嵌套的作用域天然构成一棵树
正文
符号表记录了名字和它的属性。可同一个名字,比如 x,可能在程序的不同地方指代不同的东西。编译器怎么知道某处的 x 是哪一个?
这就要引入作用域(scope):一个名字可见、有效的程序范围。
名字的“地盘”
一个名字只在它的作用域里有效。出了这个范围,别人就看不见它了。
最基本的作用域单位是代码块——一对花括号包起来的范围。比如:
{ 定义 x ... 这里能用 x }
这里就用不了 x 了
函数、类、整个程序,也都各自划出作用域。于是,程序里到处都是大大小小的“名字地盘”。
嵌套与遮蔽
作用域是可以嵌套的:一个代码块里,还能再套一个代码块。
嵌套带来一个有意思的规则,叫遮蔽(shadowing):如果内层作用域定义了一个和外层同名的名字,那么在内层里,这个名字指的就是内层那个——它把外层同名者“遮住”了。等出了内层,外层的名字又“显露”出来。
这就像同一个房间号,在不同楼层各有一间:你站在三楼报“302”,指三楼那间;走到四楼报“302”,就是四楼那间。同一个名字,在不同层次可以指不同的东西。
一棵嵌套的树
因为作用域可以一层套一层,它们天然构成一棵树:外层是父节点,内层是子节点。查找一个名字时,就从当前所在的最内层,一路向父节点(外层)找过去,直到找到最近的那个定义。
这正好决定了下一章要讲的“名字解析”该怎么做。作用域的结构,就是名字查找的路径。
思考题 1
什么是“遮蔽”?请举一个例子。
思考题 2
为什么说作用域天然是嵌套的?
小结
知识点
- 作用域是名字可见的程序范围
- 代码块、函数等都可以界定作用域
- 内层同名定义会遮蔽外层
- 嵌套的作用域构成一棵树
参考资料
- Wikipedia(zh):作用域:名字在程序中可见的范围
- Wikipedia(zh):变量遮蔽:内层同名定义遮盖外层
思考题答案(仅供参考)
思考题 1
遮蔽指内层作用域定义了一个与外层同名的名字,于是该名字在内层指向内层的定义,外层的同名定义被暂时遮住。例如外层有变量 x,内层又定义一个 x,则内层里的 x 指的是内层这个,出了内层才又是外层的 x。
思考题 2
因为代码块、函数等可以相互嵌套:一个作用域里可以再包含另一个作用域。这种一层包含一层的结构,正好对应树的父子关系,所以说作用域天然是嵌套的。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪