8. 语句
C# 提供各种语句。使用过 C 和 C++ 编程的开发人员熟悉其中大多数语句。
statement:
labeled-statement
declaration-statement
embedded-statement
embedded-statement:
block
empty-statement
expression-statement
selection-statement
iteration-statement
jump-statement
try-statement
checked-statement
unchecked-statement
lock-statement
using-statement
yield-statement
embedded-statement非终结符用于在其他语句内出现的语句。使用embedded-statement(而非statement)便不需要在这些上下文中使用声明语句和标记语句。下面的示例
void F(bool b) {
if (b)
int i = 44;
}
将导致编译时错误,原因是if语句的 if 分支要求embedded-statement而不是statement。若允许执行上述代码,则声明了变量i,却永远无法使用它。但是请注意,如果是将i的声明放置在一个块中,则该示例就是有效的。
8.1 结束点和可到达性
每个语句都有一个结束点 (end point)。直观地讲,语句的结束点是紧跟在语句后面的那个位置。复合语句(包含嵌入语句的语句)的执行规则规定了当控制到达一个嵌入语句的结束点时所采取的操作。例如,当控制到达块中某个语句的结束点时,控制就转到该块中的下一个语句。
如果执行流程可能到达某个语句,则称该语句是可到达的 (reachable)。相反,如果某个语句不可能被执行,则称该语句是不可到达的 (unreachable)。
在下面的示例中
229
void F() {
Console.WriteLine("reachable");
goto Label;
Console.WriteLine("unreachable");
Label:
Console.WriteLine("reachable");
}
第二个Console.WriteLine调用是不可到达的,这是因为不可能执行该语句。
如果编译器确定某个语句是不可到达的,将会报出警告。准确地说,语句不可到达不算是错误。
为了确定某个特定的语句或结束点是否可到达,编译器根据为各语句定义的可到达性规则进行控制流分析。控制流分析会考虑那些能控制语句行为的常量表达式(第 7.19 节)的值,但不考虑非常量表达式的可能值。换句话说,出于控制流分析的目的,给定类型的非常量表达式被认为具有该类型的任何
可能值。
在下面的示例中
void F() {
const int i = 1;
if (i == 2) Console.WriteLine("unreachable");
}
if语句的布尔表达式是常量表达式,原因是==运算符的两个操作数都是常量。由于该常量表达式在编译时进行计算并产生值false,所以Console.WriteLine调用被认为是不可到达的。但是,如果i更改为局部变量
void F() {
int i = 1;
if (i == 2) Console.WriteLine("reachable");
}
则Console.WriteLine调用被认为是可到达的,即使它实际上永远不会被执行。
函数成员的block始终被认为是可到达的。通过依次计算块中各语句的可到达性规则,可以确定任何给定语句的可到达性。
在下面的示例中
void F(int x) {
Console.WriteLine("start");
if (x < 0) Console.WriteLine("negative");
}
第二个Console.WriteLine的可到达性按下面的规则确定:
•第一个Console.WriteLine表达式语句是可到达的,原因是F方法块是可到达的。
•第一个Console.WriteLine表达式语句的结束点是可到达的,原因是该语句是可到达的。
•if语句是可到达的,原因是第一个Console.WriteLine表达式语句的结束点是可到达的。
•第二个Console.WriteLine表达式语句是可到达的,原因是if语句的布尔表达式没有常量值false。
在下列两种情况下,如果某个语句的结束点是可以到达的,则会出现编译时错误:
•由于switch语句不允许一个 switch 节“贯穿”到下一个 switch 节,因此如果一个 switch 节的语句列表的结束点是可到达的,则会出现编译时错误。如果发生此错误,则通常表明该处遗漏了一个break语句。
•如果一个计算某个值的函数成员的块的结束点是可到达的,则会出现编译时错误。如果发生此错误,则通常表明该处遗漏了一个return语句。
8.2 块
block用于在只允许使用单个语句的上下文中编写多条语句。
block:
{  statement-list opt}
block由一个扩在大括号内的可选statement-list(第 8.2.1 节)组成。如果没有此语句列表,则称块是空的。
块可以包含声明语句(第 8.5 节)。在一个块中声明的局部变量或常量的范围就是该块本身。
在块内,在表达式上下文中使用的名称的含义必须始终相同(第 7.6.2.1 节)。
块按下述规则执行:
•如果块是空的,控制转到块的结束点。
•如果块不是空的,控制转到语句列表。当(如果)控制到达语句列表的结束点时,控制转到块的结束点。
如果块本身是可到达的,则块的语句列表是可到达的。
如果块是空的或者如果语句列表的结束点是可到达的,则块的结束点是可到达的。
包含一条或多条yield语句(第 8.14 节)的block称为迭代器块。迭代器块用于以迭代器的形式实现函数成员(第 10.14 节)。某些附加限制适用于迭代器块:
writeline函数
•迭代器块中出现return语句时会产生编译时错误(但允许yield return语句)。
•迭代器块包含不安全的上下文(第 18.1 节)时将导致编译时错误。迭代器块总是定义安全的上下文,即使其定义嵌套在不安全的上下文中也如此。
8.2.1 语句列表
语句列表 (statement list) 由一个或多个顺序编写的语句组成。语句列表出现在block(第 8.2 节)和switch-block(第 8.7.2 节)中。
statement-list:
statement
statement-list  statement
执行一个语句列表就是将控制转到该列表中的第一个语句。当(如果)控制到达某条语句的结束点时,控制将转到下一个语句。当(如果)控制到达最后一个语句的结束点时,控制将转到语句列表的结束点。
如果下列条件中至少一个为真,则语句列表中的一个语句是可到达的:
•该语句是第一个语句且语句列表本身是可到达的。
•前一个语句的结束点是可到达的。
•该语句本身是一个标记语句,并且该标签已被一个可到达的goto语句引用。
如果列表中最后一个语句的结束点是可到达的,则语句列表的结束点是可到达的。
231
empty-statement什么都不做。
empty-statement:
;
当在要求有语句的上下文中不执行任何操作时,使用空语句。
执行一个空语句就是将控制转到该语句的结束点。这样,如果空语句是可到达的,则空语句的结束点也是可到达的。
当编写一个语句体为空的while语句时,可以使用空语句:
bool ProcessMessage() {...}
void ProcessMessages() {
while (ProcessMessage())
;
}
此外,空语句还可以用于在块的结束符“}”前声明标签:
void F() {
...
if (done) goto exit;
...
exit: ;
}
8.4 标记语句
labeled-statement可以给语句加上一个标签作为前缀。标记语句可以出现在块中,但是不允许它们作为嵌入语句。
labeled-statement:
identifier  :  statement
标记语句声明了一个标签,它由identifier来命名。标签的范围为在其中声明了该标签的整个块,包括任何嵌套块。两个同名的标签若具有重叠的范围,则会产生一个编译时错误。
标签可以在该标签的范围内被goto语句(第 8.9.3 节)引用。这意味着goto语句可以在它所在的块内转移控制,也可以将控制转到该块外部,但是永远不能将控制转入该块所含的嵌套块的内部。
标签具有自己的声明空间,并不影响其他标识符。下面的示例
int F(int x) {
if (x >= 0) goto x;
x = -x;
x: return x;
}
是有效的,尽管它将x同时用作参数和标签的名称。
执行一个标记语句就是执行该标签后的那个语句。
除由正常控制流程提供的可到达性外,如果一个标签由一个可到达的goto语句引用,则该标记语句是可到达的。(异常:如果goto语句在一个包含了finally块的try中,标记语句在try之外,并且finally块的结束点不可到达,则从该goto语句不可到达上述标记语句。)
declaration-statement声明局部变量或常量。声明语句可以出现在块中,但不允许它们作为嵌入语句使用。
declaration-statement:
local-variable-declaration  ;
local-constant-declaration  ;
8.5.1 局部变量声明
local-variable-declaration声明一个或多个局部变量。
local-variable-declaration:
local-variable-type  local-variable-declarators
local-variable-type:
type
var
local-variable-declarators:
local-variable-declarator
local-variable-declarators  ,  local-variable-declarator
local-variable-declarator:
identifier
identifier  =  local-variable-initializer
local-variable-initializer:
expression
array-initializer
local-variable-declaration的local-variable-type要么直接指定声明引入的变量的类型,要么通过标识符var指示应基于初始值设定项来推断该类型。此类型后接一个local-variable-declarator列表,其中每一项都引入一个新变量。local-variable-declarator由一个命名变量的identifier组成,根据需要此 identifier 后可接一个“=”标记和一个赋予变量初始值的local-variable-initializer。
在局部变量声明的上下文中,标识符 var 充当上下文关键字(第 2.4.3 节)。将local-variable-type指
定为var且作用域内没有名为var的类型时,则该声明为隐式类型化局部变量声明 (implicitly typed local variable declaration),其类型从关联的初始值设定项表达式的类型推断。隐式类型化局部变量声明受到以下限制:
•local-variable-declaration不能包含多个local-variable-declarator。
•local-variable-declarator必须包含一个local-variable-initializer。
•local-variable-initializer必须是expression。
•初始值设定项expression必须具有编译时类型。
•初始值设定项expression不能引用声明的变量本身。
下面是不正确的隐式类型化局部变量声明的示例:
var x;              // Error, no initializer to infer type from
var y = {1, 2, 3};  // Error, array initializer not permitted
var z = null;        // Error, null does not have a type
var u = x => x + 1;  // Error, anonymous functions do not have a type
var v = v++;        // Error, initializer cannot refer to variable itself
233

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。