前面已经展示了一些在方法内部创建变量的例子。变量从定义了它的语句开始存在,同一个方法内的后续语句可以使用该变量。换言之,变量只能在创建了之后才能使用。方法执行完毕后,变量也会彻底消失。
假如一个变量能在程序中的一个特定位置使用,就说明该变量具有那个位置的作用域。也就是说,一个变量的作用域(scope)是指能够使用该变量的程序区域。作用域既作用于方法,也作用于变量。一个标识符(不管它代表变量还是代表方法)的作用域是从声明明该标识符的那个位置开始的。
定义局部作用域
界定方法主体的起始与结束大括号建立了一个作用域。方法主体中声明的任何变量都具有那个方法的作用域;一旦方法结束,它们也会消失,而且只能由那个方法内部执行的代码来访问。这些变量称为局部变量(local variable),因为它们局限于声明它们的那个方法,不能在其他任何方法的作用域中使用。换言之,你不能使用局部变量在不同的方法之间共享信息。例如:
class Example { void firstMethod() { int myVar; ... } void anotherMethod() { myVar = 42; // 错误 – 变量越界 ... } } |
上述代码将编译失败,因为anotherMethod方法试图使用一个越界的myVar变量。该变量只能由firstMethod方法中的语句使用。
定义类作用域
界定类主体的起始和结束大括号也建立了一个作用域。在类主体中(但不在一个方法中)声明的任何变量都具有那个类的作用域。在C#术语中,开发者使用字段(field)一词来描述由一个类定义的变量。和局部变量不同,你可以使用字段在不同的方法之间共享信息。 例如:
class Example { void firstMethod() { myField = 42; // ok ... } void anotherMethod() { myField = 42; // ok ... } int myField = 0; } |
变量myField是在类的内部以及firstMethod和anotherMethod方法的外部定义的。所以,myField具有类的作用域,可由类中的所有方法使用。
这个例子中还需要注意另一点。在一个方法中,必须在使用一个变量前声明它。但字段稍有不同,一个方法能在定义一个字段的语句之前使用那个字段——在这种情况下,编译器将为你打点一切!
重载方法
如果两个标识符同名,而且在同一个作用域中声明,就可以说它们被重载(overloaded)。通常,重载的标识符属于一个程序bug,会在编译时被捕捉到并报错。例如,假定你在同一个方法中声明了两个同名的局部变量,就会获得一个编译时错误。类似地,假如在同一个类中声明了同名的两个字段,或者在同一个类中声明了两个完全一样的方法,就会获得一个编译时错误。这个事实表面上似乎不值一提,因为一切都会被报告为编译时错误。然而,你确实能通过一种方式来重载标识符,而且这种重载不仅是有用的,而且是重要的。
以Console类中的WriteLine方法为例,前面已经使用该方法向屏幕输出一个字符串。然而,在“代码和文本编辑器”窗口中输入WriteLine时,会自动弹出一个“智能感知”列表,其中列出了19个不同的版本!WriteLine方法的每个版本都获取一套不同的参数。一个实现不获取任何参数,只是输出一个空行;另一个实现则获取一个bool参数,并输出它的值的字符串形式(true或false);还有一个实现获取一个小数值,并以字符串的形式输出它;等。程序编译时,编译器会检查所传递的实参的类型,然后调用参数集与之匹配的一个方法版本。下面是一个例子:
static void Main() { Console.WriteLine("The answer is "); Console.WriteLine(42); } |
如果需要针对不同的数据类型执行相同的操作,重载就是一项十分有用的技术。如果不同的实现有不同的参数集,就可以考虑重载一个方法。换言之,每个版本都具有相同的方法名,但具有不同的参数数量或者不同的参数类型。利用这个功能,在调用一个方法时,可以提供一个以逗号分隔的实参列表,而编译器将根据这些实参的数量和类型,选择其中的一个匹配的重载版本。但要注意,虽然可以重载一个方法的参数,但不能重载方法的返回类型。也就是说,不能声明只是返回类型有区别的两个同名方法(编译器虽然比较聪明,但还不至于聪明到那种程度)。