语句
C 和 C++ 程序员将发现 D 的语句非常熟悉,他们还会发现有一些有趣的补充。语句: 标号语句 语句块 表达式语句 声明语句 If语句 调试语句 Version语句 While语句 DoWhile语句 For语句 Foreach语句 Switch语句 Case语句 Default语句 Continue语句 Break语句 Return语句 Goto语句 With语句 Synchronize语句 Try语句 Throw语句 Volatile语句 Asm语句 Pragma语句
Statement: LabeledStatement BlockStatement ExpressionStatement DeclarationStatement IfStatement DebugStatement VersionStatement WhileStatement DoWhileStatement ForStatement ForeachStatement SwitchStatement CaseStatement DefaultStatement ContinueStatement BreakStatement ReturnStatement GotoStatement WithStatement SynchronizeStatement TryStatement ThrowStatement VolatileStatement AsmStatement PragmaStatement
标号语句
语句可以有标号。标号是一种标志符,其后跟随一条语句。标号语句: 标志符 ':' 语句
LabelledStatement: Identifier ':' Statement包括空语句在内的任何语句都可以有标号,因此都可以作为 goto 语句的目标。标号语句也可以用作 break 或者 continue 语句的目标。
标号位于独立的名字空间中,不与声明、变量、类型等位于同一名字空间。就算如此,标号也不能同局部声明重名。标号的名字空间是它们所在的函数体。标号的名字空间不嵌套,也就是说,一个语句块中的标号可以在语句块外访问。
语句块
语句块是由{ }包括起来的语句序列。其中的语句按照词法顺序执行。语句块: { } { 语句列表 } 语句列表: 语句 语句 语句列表
BlockStatement: { } { StatementList } StatementList: Statement Statement StatementList语句块为局部符号引入了新的作用域。尽管如此,在函数内部,局部符号的名称必须唯一。
void func1(int x) { int x; // 非法,x 在函数作用域内定义了多次 } void func2() { int x; { int x; // 非法,x 在函数作用域内定义了多次 } } void func3() { { int x; } { int x; // 非法,x 在函数作用域内定义了多次 } } void func4() { { int x; } { x++; // 非法,x 未定义 } }主要的思想是避免在复杂函数内由于作用域声明而错误的覆盖了前面的名称造成 bug 。函数内部的所有名称都必须唯一。
表达式语句
表达式将被计算。表达式语句: 表达式 ;
ExpressionStatement: Expression ;没有作用的表达式,如 (x + x),在表达式语句中是非法的。
声明语句
声明语句声明并初始化变量。声明语句: 类型 标志符列表 ; 标志符列表: 变量 变量 , 标志符列表 变量: 标志符 标志符 = 赋值表达式
DeclarationStatement: Type IdentifierList ; IdentifierList: Variable Variable , IdentifierList Variable: Identifier Identifier = AssignmentExpression如果没有指明 赋值表达式 来初始化变量,变量将被初始化为它的默认值。
If 语句
If 语句提供了按条件执行语句的方法。If语句: if ( 表达式 ) 语句 if ( 表达式 ) 语句 else 语句表达式 将被计算,计算的结果必须可以被转换为布尔型。如果为 true 执行 if 之后的语句,否则执行 else 之后的语句。
所谓的‘悬挂 else’问题可以通过邻近匹配原则解决。
While 语句
While 语句实现了简单的循环结构。While语句: while ( 表达式 ) 语句
WhileStatement: while ( Expression ) Statement表达式 将被计算,计算的结果必须可以被转换为布尔型。如果为 true 执行 语句 。在 语句 被执行后,将再次计算 表达式 ,如果为 true 则再次执行执行 语句 。持续这个过程直到 表达式 结果为 false 。
break 语句将退出循环。continue 语句将直接跳转到下一次计算 表达式 。
Do-While 语句
Do-While 语句实现了简单的循环结构。Do语句: do 语句 while ( 表达式 )
DoStatement: do Statement while ( Expression )表达式 首先被执行。然后计算 表达式 ,结果必须可以被转换为布尔型。如果结果为 true 继续循环,直到 表达式 的值变为 false 。
break 语句将推出循环。continue 语句将直接进行下一轮 表达式 估值。
For 语句
For 语句实现了带有初始化、测试和递增的循环结构。For语句: for (初始化; 测试; 递增) 语句 初始化: 空 表达式 声明 测试: 空 表达式 递增: 空 表达式
ForStatement: for (Initialize; Test; Increment) Statement Initialize: empty Expression Declaration Test: empty Expression Increment: empty Expression先执行 初始化 。然后计算 测试 ,结果必须可以被转换为布尔型。如果结果为 true ,执行 语句 。在执行 语句 后,将执行 递增 。然后再次计算 测试 ,如果结果为 true 再次执行 语句 。持续这个过程直到 测试 的计算结果为 false 。
break 语句将推出循环。continue 语句将直接跳转到 递增 。
如果 初始值 声明了一个变量,变量的作用域持续到 语句 结束。例如:
for (int i = 0; i < 10; i++) foo(i);等价于:
{ int i; for (i = 0; i < 10; i++) foo(i); }函数体不能为空:
for (int i = 0; i < 10; i++) ; // 非法正确的写法是:
for (int i = 0; i < 10; i++) { }初始值 可以忽略。 测试 也可以被忽略,如果忽略,就假定为 true 。
Foreach 语句
foreach 语句遍历一个聚集的全部内容。Foreach语句: foreach (Foreach类型列表; 表达式) 语句 Foreach类型列表: Foreach类型 Foreach类型 , Foreach类型列表 Foreach类型: inout 类型 标志符 类型 标志符
ForeachStatement: foreach (ForeachTypeList; Expression) Statement ForeachTypeList: ForeachType ForeachType , ForeachTypeList ForeachType: inout Type Identifier Type Identifier先计算 表达式 的值。它的结果必须为静态数组、动态数组、关联数组、结构或类类型的聚集表达式。对于聚集表达式的每个元素执行一次 语句 。在每次遍历开始时, Foreach类型列表 声明的变量默认为聚集的内容的拷贝(即采用传值方式)。如果变量是 inout 的,这些变量就是聚集内容的引用(即采用传引用方式)。
如果聚集表达式是静态或动态数组,可以声明一个或者两个变量。如果声明了一个变量,那么这个变量就被赋予数组元素的 值 ,一个接一个。变量的类型必须同数组内容的类型匹配,除了下面提到的特殊情况以外。如果声明了两个变量,第一个变量对应于 索引 ,第二个对应于 值 。索引 必须是 int 或者 uint 类型,它不能是 inout 的,它的值是数组元素的索引。
char[] a; ... foreach (int i, char c; a) { printf("a[%d] = '%c'/n", i, c); }如果聚集表达式是 char、wchar 或者 dchar 的静态或动态数组,则 值 的 类型 可以是 char、wchar 或 dchar 中的任何一个。采用这种方式,任何 UTF 数组可以被解码为任何 UTF 类型:
char[] a = "/xE2/x89/xA0"; // /u2260 编码为 3 个 UTF-8 byte foreach (dchar c; a) { printf("a[] = %x/n", c); // 打印 'a[] = 2260' } dchar[] b = "/u2260"; foreach (char c; b) { printf("%x, ", c); // 打印 'e2, 89, a0' }如果聚集表达式是关联数组,可以声明一个或者两个变量。如果声明了一个变量,那么这个变量就被赋予数组元素的 值 ,一个接一个。变量的类型必须同数组内容的类型匹配。如果声明了两个变量,第一个变量对应于 索引 ,第二个对应于 值 。 索引 必须同关联数组的索引具有相同的类型,它不能是 inout ,它的值是数组元素的索引。
double[char[]] a; // 索引 类型是 char[],值 类型是 double ... foreach (char[] s, double d; a) { printf("a['%.*s'] = %g/n", s, d); }如果聚集表达式是一个静态或者动态数组,将从索引 0 到 数组的最大索引 遍历数组元素。如果是关联数组,则元素的顺序是未定义的。如果是一个结构或者一个类对象,访问的顺序由它们的 opApply 成员函数定义。
如果聚集是一个结构或者类对象,则它必须定义具有如下类型的 opApply 函数:
int opApply(int delegate(inout Type [, ...]) dg);其中 Type 要同 foreach 声明中 标志符 中的 类型 匹配。传递给 opApply 的委托类型中剩下的那些 类型 (上式中的“...”)对应于其余的多个 Foreach类型 。结构或类中可以有多个不同的 opApply 函数, foreach语句 会使用类型匹配的函数,该函数的参数 dg 的参数类型要同 foreach语句 中对应的 Foreach类型 匹配。 opApply 的函数体遍历聚集的元素,并将它们作为参数依次传递给 dg 函数。如果 dg 返回 0 ,则 opApply 继续应用于下一个元素。如果 dg 返回一个非零值,opApply 必须停止遍历并返回那个值。如果 opApply 顺利遍历了所有的元素,会返回 0 。
例如,考虑包含两个元素的容器类:
class Foo { uint array[2]; int opApply(int delegate(inout uint) dg) { int result = 0; for (int i = 0; i < array.length; i++) { result = dg(array[i]); if (result) break; } return result; } }一个可能的例子是:
void test() { Foo a = new Foo(); a.array[0] = 73; a.array[1] = 82; foreach (uint u; a) { printf("%d/n", u); } }将打印出:
73 82聚集可以是字符串文字量,这样的聚集可以看作 char、wchar 或 dchar 数组来访问:
void test() { foreach (char c; "ab") { printf("'%c'/n", c); } foreach (wchar w; "xy") { wprintf("'%c'/n", w); } }将打印出:
'a' 'b' 'x' 'y'inout 用来更新数据项:
void test() { static uint[2] a = [7, 8]; foreach (inout uint u; a) { u++; } foreach (uint u; a) { printf("%d/n", u); } }将打印出:
8 9在 foreach 遍历所有数据项时,不能修改聚集本身,不能重新设定聚集大小、重新分配、释放、重新赋值或析构。
int[] a; int[] b; foreach (int i; a) { a = null; // 错误 a.length += 10; // 错误 a = b; // 错误 } a = null; // ok如果在 foreach 体内有 Break语句 ,则会退出 foreach ,而 Continue语句 将立即开始下一轮遍历。