C#图解教程 第九章 语句

语句

什么是语句


  • 语句是描述某个类型或让程序执行某动作的源代码指令。
  • 语句主要有3类
    • 声明语句 声明类型或变量
    • 嵌入语句 执行动作或管理控制流
    • 标签语句 控制跳转

例:语句示例

int x=19;       //简单声明
int z;          //简单声明
{               //
    int y=20;   //简单声明
    z=x+y;      //嵌入语句
top:y=30;       //标签语句
    ...
    {           //嵌套块
        ...
    }           //结束嵌套块
}               //结束外部块

块在语法上算作单条嵌入语句。任何语法上需要一个嵌入语句的地方,都可以使用块。

空语句仅由一个分号组成。当程序逻辑不需要任何动作时使用。

if(x<y)
    ;
else
    z=a+b;

控制流语句


C#提供与现代编程语言相同的控制流结构。

  • 条件执行依据一个条件执行或跳过一个代码片段。条件执行语句如下:
    • if
    • if…else
    • switch
  • 循环语句重复执行一个代码片段。循环语句如下:
    • while
    • do
    • for
    • foreach
  • 跳转语句把控制流从一个代码片段改变到另一个代码片段中的指定语句。跳转语句如下:
    • break
    • continue
    • return
    • goto
    • throw

条件执行和循环结构(foreach)需要一个测试表达式或条件以决定程序应当在哪里继续执行。

与C和C++不同,测试表达式必须返回bool型值。数字在C#中没有布尔意义。

if语句


if语句实现按条件执行。if语句的语法如下所示。

  • TestExpr必须计算成bool型值
  • 如果TestExpr求值为true,执行Statement
  • 如果求值为false,则跳过Statement
if(TestExpr)
    Statement

例:if语句示例

//With a simple statement
if(x<10)
    z=x-1;//简单语句不需要大括号
//With a block
if(x>=20)
{          //块需要大括号
    x-=5;
    y=x+z;
}
int x=5;
if(x)      //错:表达式必须是bool型,而不是int型
{
    ...
}

if…else语句


if…else语句实现双路分支。if…else语句的语法如下。

  • 如果TestExpr求值为true,执行Statement1
  • 如果求值为flase,执行Statement2
if(TestExpr)
    Statement1
else
    Statement2


例:if…else示例

if(x<=10)
    z=x-1;    //简单语句
else
{             //多条语句组成的语句块
    x-=5;
    y=x+z;
}

if…else或if语句都可以嵌套。如果你在阅读嵌套if…else语句代码时,要找出else属于哪个if,有个简单的规则。每个else都属于离它最近的前一条没有相关else字句的if语句。

while循环


while是一种简单循环结构,其测试表达式在循环的顶部执行。while循环语法如下。

  • 首先对TestExpr求值
  • 如果TestExpr求值为false,将继续执行在while循环结尾之后的语句
  • 当TestExpr求值为true时,执行Statement,并且再次对TestExpr求值。每次TestExpr求值为true时,Statement都要在执行一次。循环在TestExpr求值为false时结束
while(TestExpr)
    Statement


例:while循环示例

int x=3;
while(x>0)
{
    Console.WriteLine("x:{0}",x);
    x--;
}
Console.WriteLine("Out of loop");

do循环


do循环是一种简单循环结构,其测试表达式在循环的底部执行。do循环语法如下。

  • 首先,执行Statement
  • 然后,对TestExpr求值
  • 如果TestExpr返回true,那么再次执行Statement
  • 每次TestExpr返回true,都将再次执行Statement
  • 当TestExpr返回false时,控制传递到循环结构结尾之后的那条语句

do循环有几个特征,使它与其他控制流结构相区分。

  • 循环体Statement至少执行一次,即使TestExpr初始为false,这是因为在循环底部才会对TestExpr求值
  • 在测试表达式的关闭圆括号之后需要一个分号
do
    Statement
while(TestExpr);    //结束do循环


例:do循环示例

int x=0;
do
    Console.WriteLine("x is {0}",x++);
while(x<3);

for循环


只要测试表达式在循环体顶端计算时返回true,for循环结构就会执行循环体。for循环语法如下。

  • 在for循环的开始,执行一次Initializer
  • 然后对TestExpr求值
  • 如果它返回true,执行Statement,接着是IterationExpr
  • 然后控制回到循环顶端,再次对TestExpr求值
  • 只要TestExpr返回true,Statement和IterationExpr都将被执行
  • 一旦TestExpr返回false,就继续执行Statement之后的语句
for(Initializer;TestExpr;IterationExpr)
    Statement

语句中的一些部分是可选的,其他部分是必需的。

  • Initializer、TestExpr和IterationExpr都是可选的。它们位置可以空着。如果TestExpr位置是空的,那么测试被假定返回true。因此程序要避免进入无限循环,必须有某种其他退出该语句的方法
  • 作为字段分隔符,两个分号是必需的,即使其他部分都省略了

  • Initializer只执行一次,在for结构的任何其他部分之前。它常用于声明和初始化循环中使用的本地变量
  • 对TestExpr求值以决定应该执行Statement还是跳过。它必须计算成bool类型的值。如前所述,如果TestExpr为空,将永远返回true
  • IterationExpr在Statement之后并且在返回到循环顶端TestExpr之前立即执行

例:for循环示例

for(int i=0;i<3;i++)
{
    Console.WriteLine("Insile loop. i: {0}",i);
}
Console.WriteLine("Out of loop");

for语句中变量的作用域

任何声明在Initializer中的变量只在该for语句的内部可见。

  • 这与C和C++不同,C和C++中声明把变量引入到外围的块
  • 下面的代码阐明了这点
这里需要类型来声明
     ↓
for(int i=0;i<10;i++)  //变量i在作用域内
    Statement;         //语句
                       //在该语句之后,i不再存在
这里仍需要类型,因为前面的变量i已经超出存在范围
     ↓
for(int i=0;i<10;i++)  //我们需要定义一个新的i变量
    Statement;         //因为先前的i变量已经不存在

在循环体内部声明的变量只能在循环体内部使用。

循环变量常常使用标识符i、j、k。这是早年FORTRAN程序的传统。在FORTRAN中,以字母I、J、K、L、M、N开头的标识符默认为INTEGER类型,没有必要声明。由于循环变量通常为整型,程序员简单地使用I作为循环变量的名称,并把它作为一种约定。这简短易用,而且不用声明。如果存在嵌套循环,内层循环变量通常为J。如果还有内层嵌套循环,就用K。

初始化和迭代表达式中的多表达式

初始化表达式和迭代表达式都可以包含多个表达式,只要它们用逗号隔开。

例:下面代码,初始化表达式中有两个变量声明,迭代表达式中有两个表达式

class Program
{
    static void Main()
    {
        const int MaxI=5;
        for(int i=0,j=10;i<MaxI;i++,j+=10)
        {
            Console.WriteLine("{0},{1}",i,j);
        }
    }
}

switch语句


switch实现多路分支

  • switch包含0个或多个分支
  • 每个分支以一个或多个分支标签开始
  • 每个分支的末尾必须为break或其他4种跳转语句
    • 跳转语句包括 break、return、continue、goto、throw
    • break语句将执行过程跳转到switch的尾部

分支标签按顺序求值。如果某标签与测试表达式的值匹配,就执行该分支,然后跳到switch尾部


分支示例

例:下面代码通过for循环,执行switch语句5次

for(int x=1;x<6;x++)
{
    switch(x)
    {
        case 2:
            Console.WriteLine("x is {0} -- In Case 2",x);
            break;
        case 5:
            Console.WriteLine("x is {0} -- In Case 5",x);
            break;
        default:
            Console.WriteLine("x is {0} -- In Default case",x);
            break;
    }
}

switch语句补充

switch语句可以有任意多分支,包括没有分支。default不是必需的,然而通常认为拥有default是好习惯,因为它可以捕获潜在错误。
例:没有default

for(int x=1;x<6;x++)
{
    switch(x)
    {
        case 5:
            Console.WriteLine("x is {0} -- In Case 5",x);
            break;
    }
}

例:只有default

for(int x=1;x<4;x++)
{
    switch(x)
    {
        default:
            Console.WriteLine("x is {0} -- In Default Case",x);
            break;
    }
}

分支标签

case后的分支标签必须是常量表达式,即必须在编译时完全获取运算结果。

和C/C++不同,每个switch段,包括可选的default段,必须以一个跳转语句结尾。在C#中,不可以执行一个switch段中的代码然后直接执行接下来的部分。

尽管C#不允许从一个分支到另一个分支的导向,你仍然可以把多个分支标签附加到任意分支。

switch(x)
{
    case 1:
    case 2:        //可接受的
        ...
        break;
    case 5:
        y=x+1;
    case 6:        //因为没有break,所以不可接受
        ...
}

跳转语句


控制流到达跳转语句时,程序执行被无条件转移到程序的另一部分。

  • break
  • continue
  • return
  • goto
  • throw

这一章阐述前4条语句,throw在第11章讨论

break语句


前面你已经看到break被用在switch中。它还能被用在下列语句中:

  • for
  • foreach
  • while
  • do

break导致执行调出最内层封装语句(innermost enclosing statement)

例:break+while

int x=0;
while(true)
{
    x++;
    if(x>=3)
        break;
}

continue语句


continue语句导致程序执行转到下列类型循环的最内层封装语句的顶端:

  • while
  • do
  • for
  • foreach

例:下例for循环在前3次迭代,它遇到continue直接返回循环顶部,错过WriteLine语句

for(int x=0;x<5;x++)
{
    if(x<3)
        continue;
    Console.WriteLine("Value of x is {0}",x);
}

标签语句


标签语句格式:

Identifier:Statement

标签语句的执行如果标签不存在,仅执行Statement部分。

  • 给语句增加一个标签允许控制从代码其他部分转移到该语句
  • 标签语句只允许用在块内部
标签

标签有自己的声明空间,所以标签的标识符可以与本地变量或参数名重复。

{
    int xyz=0;
    ...
    xyz:Console.WriteLine("No problem.");
}
标签语句作用域

标签语句作用域为:

  • 它声明所在的块
  • 任何嵌套在该内部的块

goto语句


goto语句无条件转移控制到一个标签语句。

goto Identifier;

bool thingsAreFine;
while(true)
{
    thingsAreFine=GetNuclearReactorCondition();
    if(thingsAreFine)
        Console.WriteLine("Things are fine.");
    else
        goto NotSoGood;
}
NotSoGood:Console.WriteLine("We have a problem.");

goto语句必须在标签语句的作用域内。

  • goto语句可以跳到它本身所在块内的任何标签语句,或跳出到任何它被嵌套的块内的标签语句
  • goto语句不能跳入任何嵌套在该语句本身所在块内部的任何块

警告:使用goto语句是非常不好的,因为它会导致弱结构化的、难以调试和维护的代码。EdsgerDijkstra在1968年给Communication of the ACM写了一封信,标题为“Go To Statement Considerred Harmful”,是对计算机科学非常重要的贡献。它是最先发表的描述使用goto语句缺陷的文章之一。

goto语句在switch语句内部

goto case ConstantExpression;//跳转到指定case段
goto default;                //跳转到default段

using语句


有些类型的非托管对象数量有限制或很耗费系统资源。在使用完它们后,尽可能快地释放它们是非常重要的。using语句有助于简化该过程并确保这些资源被适当的处置(dispose)。
资源是指一个实现了System.IDisposable接口的类或结构。接口在第15章详述,但简而言之,接口就是未实现的函数成员的集合,类和结构可以选择去实现。IDisposable接口含有单独一个名称为Dispose的方法。

使用资源的阶段如下图:

  • 分配资源
  • 使用资源
  • 处置资源

如果在正在使用资源的那部分代码中产生一个意外的运行时错误,那么处置资源的代码可能得不到执行。


using语句不同于using指令。using指令在第21章阐述。

资源的包装使用

using语句帮助减少意外的运行时错误带来的潜在问题,它整洁地包装了资源的使用。
有两种形式的using语句。第一种形式如下:

  • 圆括号内的代码分配资源
  • Statement是使用资源的代码
  • using语句隐式产生处置该资源的代码
using(ResourceType Identifier=Expression)//分配资源
{
    Statement//使用资源
}

第二种形式放在异常处理的finally块中。

  • 分配资源
  • 把Statement放进try块
  • 创建资源的Dispose方法的调用,并把它放进finally块

using语句示例
  • TextWriter资源打开一个文本文件,并向文件写入一行
  • TextReader资源接着打开相同的文本文件,一行一行读取并显式它的内容
  • 在两种情况中,using语句确保调用对象的Dispose方法
  • 还要注意Main中using语句和开始两行的using指令之间的区别
using System;    //using指令,不是using语句
using System.IO; //using指令,不是using语句
namespace UsingStatement
{
    class Program
    {
        static void Main()
        {
            //using语句
            using(TextWriter tw=File.CreateText("Lincoln.txt"))
            {
                tw.WriteLine("Four score and seven years ago,...");
            }
            //using语句
            using(TextReader tr=File.OpenText("Lincoln.txt"))
            {
                string InputString;
                while(null!=(InputString=tr.ReadLine()))
                {
                    Console.WriteLine(InputString);
                }
            }
        }
    }
}

多个资源和嵌套

using语句还可以用于相同类型的多个资源,资源声明用逗号隔开。

    只有一个类型       资源       资源
         ↓             ↓         ↓
using(ResourceType Id1=Expr1,Id2=Expr2,...)
{
    EmbeddedStatement
}

例:

class Program
{
    static void Main()
    {
        using(TextWriter tw1=File.CreateText("Lincoln.txt"),
                         tw2=File.CreateText("Franklin.txt"))
        {
            tw1.WriteLine("Four score and seven years ago,...");
            tw2.WriteLine("Early to bed;Early to rise...");
        }
        ...
    }
}

using语句还可以嵌套。

using(TextWriter tw1=File.CreateText("Lincoln.txt"))
{
    tw1.WriteLine("Four score and seven years ago,...");
    using(TextWriter tw2=File.CreateText("Franklin.txt"))
    {
        tw2.WriteLine("Early to bed;Early to rise...");
    }
}
using语句的另一种形式
关键字   资源
  ↓       ↓
using(Expression)
{
    EmbeddedStatement//使用资源
}

这种形式中,资源在using语句前声明。

TextWriter tw=File.CreateText("Lincoln.txt");
using(tw)
{
    tw.WriteLine("Four score and seven years ago,...");
}

虽然这种形式也能确保对资源的使用结束后总是调用Dispose方法,但它不能防止在using语句已经释放了它的非托管资源后使用该资源,导致了不一致的状态。因此它提供了较少的保护,不推荐使用。


其他语句


还有一些语句和语言的特征相关。这些语句在涉及相应特征的章节中阐述。

语句 描述 相关章节
checked,unchecked 控制溢出检查上下文 第16章
foreach 遍历一个集合的每个成员 第12章和第18章
try、throw、finally 处理异常 第22章
return 将控制返回到调用函数的成员,而且还能返回一个值 第5章
yield 用于迭代 第18章