范围规则定义着编程语言中的名字的可见性。即当你在程序的不同部分引用了一个名字为 k 的变量,这些引用是指向同一个变量,还是不同的变量。就像在下面的源码中两个地方定义了x,在b过程中把2写入x,它会写入 x1 还是 x2?
program a() {
x: integer; // 代号 x1
x = 1;
procedure b() {
x = 2; // <-- 这里写入会写到那个x里面去
}
procedure c() {
x: integer; // 代号 x2
b();
}
c();
print x;
}
大部分语言, 包括Alogol, Ada, C,Pascal, Haskell, Python, Java 都是静态范围,也就是词法范围。一个块就定义着一个新的范围。在这个块里声明的变量在块的外面是看不见的。在这个范围之外的变量只有被覆盖时在这个封闭范围内才是可见的。在Algol, Pascal,Haskell,和Scheme ( 不包括 C 和 Ada )中,这些范围规则也适用于函数和过程的名称。在编译的时候我们就能基于程序的结构而确定指向的变量,在上面的例子中,定义 b 函数时范围内最里层的x定义是 x1,所以写入会指向 x1,最后运行会输出 2。
早期的 Lisp 和一些老的解释型语言像 SNOBOL 和 APL 等使用了动态范围。使用这种规则时,我们首先去找变量的 local definition, 也就是所在块里面有没有定义。如果没有就去程序的 calling stack(调用栈)去找这个变量的定义。在上面的例子中,开始运行 a 函数时会把 x => x1 压到运行栈里,调用 c 函数会把 x => x2 压到运行栈里,当我们运行到 b 函数时,运行栈最上面的是 x => x2, 所以会把 2 写入到 x2 里面。这时是访问不到 x2 的。最后运行会输出