7.7 变量作用域
现在你已经看过Scala内建的控制结构,我们将在本节用它们来解释Scala的变量作用域。
Java程序员的快速通道
如果你是Java程序员,会发现Scala的作用域规则几乎跟Java完全一样。Java和Scala的一个区别是Scala允许你在嵌套的作用域内定义同名的变量。所以如果你是Java程序员,最好至少是快速地扫一遍本节的内容。
Scala程序的变量在声明时附带了一个规定在哪里能使用这个名称的作用域(scope)。关于作用域最常见的例子是花括号一般都会引入一个新的作用域,因此任何在花括号中定义的元素都会在右花括号之后离开作用域。[6]我们可以来看看示例7.18中的函数。
示例7.18 打印乘法表时的变量作用域
示例中7.18中的printMultiTable将打印出乘法表。[7]函数的第一个语句引入了名为i的变量并初始化成整数1,然后你就可以在函数的余下部分使用i这个名称。
printMultiTable函数的下一条语句是while循环:
这里能用i,因为它仍在作用域内。while循环中的第一条语句又引入了另一个名为j的变量,还是初始化成整数1。由于变量j是在while循环的花括号中定义的,只能在while循环当中使用它。如果你在while循环的右花括号之后(即那行提示你j、prod和k已超出作用域的注释之后)还尝试对j做任何操作,你的程序将无法编译。
本例中定义的所有变量(i、j、prod、k)都是局部变量。这些变量只在定义它们的函数内“局部”有效。函数每次被调用,都会使用全新的局部变量。
变量一旦定义好,就不能在相同的作用域内定义相同名字的新变量。举例来说,下面这段有两个名为a的变量的脚本是无法通过编译的:
不过,可以在一个内嵌的作用域内定义一个跟外部作用域中相同名称的变量。比如下面的脚本可以正常编译和运行:
这段脚本执行时,会先打印2然后打印1,这是因为在花括号中定义的a是不同的变量,这个变量只在右花括号结束之前处于作用域内。[8]需要注意Scala跟Java的一个区别是,Scala不允许你在内嵌的作用域使用一个跟外部作用域内相同名称的变量。在Scala程序中,内嵌作用域中的变量会遮挡(shadow)外部作用域中相同名称的变量,因为外部作用域的同名变量在内嵌作用域内将不可见。
你可能已经注意到如下在解释器中类似遮挡的行为:
在解释器中,可以随心地使用变量名。其他的先不谈,单这一点,让你能够在不小心定义错了某个变量之后改变主意。你之所以能这样做,是因为从概念上讲,解释器会对你录入的每一条语句创建一个新的作用域。因此,可以像这样来看待被解释后的代码:
这段代码能够作为Scala脚本正常编译和运行,并且跟键入到解释器中的代码一样,会打印出2。请记住这样的代码对于阅读者来说会很困惑,因为变量在内嵌的作用域内是不同的含义。通常更好的做法是选一个新的有意义的变量名,而不是(用同样的名称)遮挡某个外部作用域的变量。