2.11 语句
函数是由语句构成的。语句按照出现的字面顺序执行。语句块则是包含在大括号({})中的一系列语句。
2.11.1 声明语句
变量声明语句可以声明新的变量,并可以用表达式初始化变量。我们可以用逗号分隔的列表声明多个同类型的变量:
常量的声明和变量类似,但是它的值无法在声明之后改变,并且变量初始化必须和声明同时进行(请参见3.1.2节):
局部变量
局部变量和常量的作用范围在当前的语句块中。在当前语句块或者嵌套的语句块中无法声明同名的局部变量:
变量的作用范围是其所在的整个代码块(包括前向和后向)。这意味着虽然在变量或常量声明之前引用它是不合法的,但即使将示例中的x初始化移动到方法的末尾我们也会得到相同的错误,这个奇怪的规则和C++是不同的。
2.11.2 表达式语句
表达式语句既是表达式也是合法的语句。表达式语句必须改变状态或者执行某些可能改变状态的调用。状态改变本质上指改变一个变量的值。可能的表达式语句有:
· 赋值表达式(包括自增和自减表达式)
· (有返回值的和没有返回值的)方法调用表达式
· 对象实例化表达式
例如:
即使调用的构造器或方法有返回值,也并不一定要使用该值。因此除非构造器或方法改变了某些状态,否则以下这些语句完全没有用处:
2.11.3 选择语句
C#使用以下几种机制来有条件地控制程序的执行流:
· 选择语句(if、switch)
· 条件语句(?:)
· 循环语句(while、do..while、for和foreach)
本节将介绍两种最简单的结构:if语句和switch语句。
2.11.3.1 if语句
if语句在bool表达式为真时执行其中的语句,例如:
if中的语句可以是代码块:
2.11.3.2 else子句
if语句之后可以紧跟else子句:
在else子句中,能嵌套另一个if语句:
2.11.3.3 用大括号改变执行流
else子句总是与它之前的语句块中紧邻的未配对的if语句结合:
语义上等价于:
可以通过改变大括号的位置来改变执行流:
大括号可以明确表明结构,这能提高嵌套if语句的可读性(虽然编译器并不需要)。需要特别指出的是下面的模式:
这里,我们参照其他语言的“elseif”结构(以及C#本身的#elif预处理指令)来安排if和else语句。Visual Studio会自动识别这个模式并保持代码缩进。从语义上讲,紧跟着每一个if语句的else语句从功能上都是嵌套在else子句之中的。
2.11.3.4 switch语句
switch语句可以根据变量可能的取值来转移程序的执行。switch语句可以拥有比嵌套if语句更加简洁的代码,因为switch语句仅仅需要一次表达式计算:
这个例子演示了最一般的情形,即针对常量的switch。当指定常量时,只能指定内置的整数类型、bool、char、enum类型以及string类型。
每一个case子句结束时必须使用某种跳转指令显式指定下一个执行点(除非你的代码本身就是一个无限循环),这些跳转指令有:
· break(跳转到switch语句的最后)
· goto case x(跳转到另外一个case子句)
· goto default(跳转到default子句)
· 其他的跳转语句,例如,return、throw、continue或者goto label
当多个值需要执行相同的代码时,可以按照顺序列出共同的case条件:
switch语句的这种特性可以写出比多个if-else更加简洁的代码。
2.11.3.5 按类型switch
按照类型进行switch是带有模式的switch语句的一种特殊的使用情况,最近几版C#语言引入了多种模式。有关模式的完整讨论,请参见4.13节。
C#支持按类型switch(从C# 7开始):
(object类型允许其变量为任何类型,这部分内容将在3.2节和3.3节详细讨论。)
每一个case子句都指定了一种需要匹配的类型和一个变量(模式变量),如果类型匹配成功就对变量赋值。和常量不同,子句对可用的类型并没有进行任何限制。
when关键字可用于对case进行预测,例如:
case子句的顺序会影响类型的选择(这和选择常量的情况有些不同)。如果交换case的顺序,则上述示例可以得到完全不同的结果(事实上,上述程序甚至无法编译,因为编译器发现第二个case子句是永远不会执行的)。但default子句是一个例外,不论它出现在什么地方都会在最后才执行。
如果希望按照类型进行switch,但对其值却并不关心,这种情况下可以使用“丢弃”变量(_):
堆叠多个case子句也是没有问题的。在下面的例子中,Console.WriteLine会在任何浮点类型的值大于1000时执行:
在上述例子中,编译器仅允许在when子句中使用模式变量f、d和m。当调用Console.WriteLine时,我们并不清楚到底三个模式变量中的哪一个会被赋值,因而编译器会将它们放在作用域之外。
除此以外,还可以混合使用常量选择和模式选择,甚至可以选择null值:
2.11.3.6 switch表达式
从C# 8开始,我们可以在表达式中使用switch。以下示例展示了该功能的使用方法,其中,假定变量cardNumber是int类型:
注意,switch是在变量名称之后出现的,且其中的case子句相应地变为了以逗号结尾的表达式(而不再是语句)。switch表达式相比switch语句更加紧凑,且可以用于LINQ查询(请参见第8章)。
如果在switch表达式中忽略默认表达式(_)同时其他条件匹配失败,则会抛出一个异常。
switch表达式也支持多变量的选择(元组模式):
switch表达式与各种模式组合可以获得更多的选择效果,详情请参见4.13节。
2.11.4 迭代语句
C#中可以使用while、do-while、for和foreach语句重复执行一系列语句。
2.11.4.1 while和do-while循环
while循环在bool表达式为true的情况下重复执行循环体中的代码。该表达式在循环体执行之前进行检测。例如,以下示例将输出012:
do-while循环在功能上不同于while循环的地方是前者在语句块执行之后才检查表达式的值(保证语句块至少执行过一次)。以下是用do-while循环重新书写上述例子:
2.11.4.2 for循环
for循环就像一个有特殊子句的while循环,这些特殊子句用于初始化和迭代循环变量。for循环有以下三个子句:
每一个子句的作用如下:
初始化子句
在循环之前执行,初始化一个或多个迭代变量。
条件子句
它是一个bool表达式,当取值为true时,将执行循环体。
迭代子句
在每次语句块迭代之后执行,通常用于更新迭代变量。
例如,下面的例子将输出0到2的数字:
下面的代码将输出前10个斐波那契数(每一个数都是前面两个数的和):
for语句的这三个部分都可以省略,因而可以通过下面的代码来实现无限循环(也可以用while (true)来代替):
2.11.4.3 foreach循环
foreach语句遍历可枚举对象的每一个元素,.NET中大多数表示集合或元素列表的类型都是可枚举的。例如,数组和字符串都是可枚举的。以下示例从头到尾枚举了字符串中的每一个字符:
以上程序的输出为:
我们将在4.6节详细介绍可枚举对象。
2.11.5 跳转语句
C#的跳转语句有break、continue、goto、return和throw。
跳转语句仍然遵守try语句的可靠性规则(参见4.5节),因此:
· 若跳转语句跳转到try语句块之外,则它总是在达到目标之前执行try语句的finally语句块。
· 跳转语句不能从finally语句块内跳到块外(除非使用throw)。
2.11.5.1 break语句
break语句用于结束迭代或switch语句的执行:
2.11.5.2 continue语句
continue语句放弃循环体中后续的语句,继续下一轮迭代。例如,以下的循环跳过了偶数:
2.11.5.3 goto语句
goto语句将执行点转移到语句块中的指定标签处,格式如下:
或用于switch语句内:
标签语句仅仅是代码块中的占位符,位于语句之前,用冒号后缀表示。下面的代码模拟for循环来遍历从1到5的数字:
goto case case-constant会将执行点转移到switch语句块中的另一个条件上(参见2.11.3.4节)。
2.11.5.4 return语句
return语句用于退出方法。如果这个方法有返回值,则必须返回方法指定返回类型的表达式。
return语句能够出现在方法的任意位置(除finally块中),并且可以多次出现。
2.11.5.5 throw语句
throw语句抛出异常来表示有错误发生(参见4.5节):
2.11.6 其他语句
using语句用一种优雅的语法在finally块中调用实现了IDisposable接口对象的Dispose方法(请参见4.5节和12.1节)。
C#重载了using关键字,使它在不同上下文中有不同的含义。特别地,using指令和using语句是不同的。
lock语句是调用Mintor类型的Enter和Exit方法的简化写法(请参见第14章和第23章)。