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