《C#图解教程》拾遗十-语句

一、什么是语句

二、表达式语句

三、控制流语句

四、if语句

五、if...else语句

六、while循环

七、do循环

八、for循环

1、for语句中变量的作用域

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

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

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

执行结果:

九、switch语句

switch语句实现多路分支:

  • switch语句有一个通常被称为测试表达式或 匹配表达式的参数。之前,这些测试表达式必须是以下数据类型之一:char、string、bool、integer(包括byte、int或long等)或enum,现在C#7.0允许测试表达式为任何类型;
  • switch语句包含0个或多个分支块;
  • 每个分支块都以一个或多个分支标签开头,每个分支标签(或者最后一个分支标签,如果一个分支块中有多个分支标签的话)后面跟着一个模式表达式,该模式表达式将与测试表达式进行比较。如果测试表达式和模式表达式都是整数类型,则使用C#的相等运算符(==)进行比较。在所有其他情况下,则使用静态方法Object.Equals(test,pattern)进行比较。也就是说,对于非整数类型,C#使用深度比较;
  • 每个分支块必须遵守“不穿过规则”,这意味着分支块中的表达语句不能到达终点并且进入下一个分支块。此规则通常通过使用break语句或其他4个跳转语句来结束表达语句列表来实现。但请注意,goto跳转语句不能与非常量switch表达式一起使用;
    • 跳转语句包括break、return、continue、goto和throw;
    • 在这5个用来结束一个分支块的跳转语句中,break语句是最常用的,break语句会切换执行流程到switch语句的末尾;
  • 分支块会按顺序执行,如果其中一个分支块与测试表达式的值匹配,则执行这个分支块,然后控制流会跳转到该分支块中使用的跳转语句指定的位置。由于break语句时最常用的跳转语句,所以通常控制流会跳转到switch语句结束后的第一行可执行代码;

如上图:

  • 测试表达式(也称匹配表达式)TestExpr在结构的顶端求值;
  • 如果TestExpr的值等于第一个分支标签中的模式表达式PatternExpr1的值,将执行该分支标签后边的语句列表,直到遇到一个跳转语句;
  • default分支是可选的,但如果包括了,就必须以一条跳转语句结束;

1、分支示例

static void Main()
{
    for(int x = 1; x < 6; x++)
    {
        switch(x)                           //测试表达式
        {
            case 2:
                Console.WriteLine($"x is {x} -- In Case 2");
                break;
            case 5:
                Console.WriteLine($"x is {x} -- In Case 5");
                break;
            default:
                Console.WriteLine($"x is {x} -- In Default case");
                break;
        }
    }
}

执行结果:

2、其他类型的模式表达式

case标签由关键字case和其后边的模式构成,模式可以是简单的值,例如Hello或者55,也可以是一个计算简单值的表达式,或者一个类型。模式可以通过使用关键字when来包含一个过滤器。

public abstract class Shape { }
public class Square : Shape
{
    public double Side { get; set; }
}
public class Circle : Shape
{
    public double Radius { get; set; }
}
public class Triangle : Shape
{
    public double Height { get; set; }
}
class _9_2OtherSchemaExpressions
{
    static void Main()
    {
        var shapes = new List<Shape>();
        shapes.Add(new Circle { Radius = 7 });
        shapes.Add(new Square { Side = 5 });
        shapes.Add(new Triangle { Height = 4 });
        var nullSquare = (Square)null;
        shapes.Add(nullSquare);

        foreach(var shape in shapes)
        {
            switch(shape)       //判断类型或者shape变量的值
            {
                case Circle circle:     //等价于if(shape is Circle)
                    Console.WriteLine($"This shape is a circle of radius {circle.Radius}");
                    break;
                case Square square when square.Side > 10:   //仅仅匹配一部分Square
                    Console.WriteLine($"This shape is a large square of side {square.Side}");
                    break;
                case Square square:
                    Console.WriteLine($"This shape is a square of side {square.Side}");
                    break;
                case Triangle triangle:
                    Console.WriteLine($"This shape is a triangle of side {triangle.Height}");
                    break;
                //case Triangle triangle when triangle.Height < 5:    //编译错误:不可访问,它已由上一case处理或无法匹配
                //    Console.WriteLine($"This shape is a triangle of side {triangle.Height}");
                //    break;
                case null:
                    Console.WriteLine($"This shape could be a Square,Circle or a Triangle");
                    break;
                default:
                    throw new ArgumentException(
                        message: "shape is not a recognized shape",
                        paramName: nameof(shape));
            }
        }
    }
}

执行结果:

上述代码中,注释掉的代码将导致编译错误,因为永远不会到达这个case,它是前一个一般case的受限的case。

此switch示例还演示了匹配变量(circle、square、triangle)的使用方法,测试表达式(shape)会立即赋值给它们。每个此类变量都是在自己的范围内,直到达到下一个跳转语句(在本例中为break)。它不会在任何其他块的范围内。

case Square s:
case Circle c:
    Console.WriteLine($"Square has dimensions: {s.Side} * {s.Side});
    Console.WriteLine($"Found a Circle of radius {c.Radius}");
    break;

上述代码也会导致编译错误,如果在同一个分支块中存在多个类型模式,则无法在编译时确定将匹配哪个模式,也无法确定将填充哪个变量。因此,你不能在构成该块的语句中使用这些变量,因为它们可能会导致空引用异常。

还要注意,不必在所有的case表达式中都只使用常量值或者常量类型,可以将它们混合在一起。

3、switch语句的补充

一个switch语句可以有任意数目的分支,也可以没有分支。default段不是必须的。

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

执行结果:

上述代码中没有default代码段,然而,通常认为拥有default段是好习惯,因为它可以捕获潜在的错误。

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

执行结果:

4、分支标签

分支标签中的case关键字后边的表达式可以是任何类型的模式,C#7.0以前,它必须是常量表达式,所以它必须在编译时被编译器计算,现在这个约束已不适用。

说明:

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

尽管C#不允许从一个分支直接进入另一个分支,但你可以把多个分支标签附加到任意分支,只要这些分支标签之间没有插入可执行语句。

switch(x)
{
    case 1:            //可接受的
    case 2:
    case 3:
    case 4:
        ……             //如果x等于1、2、3、4,则执行该代码
        break;
    case 5:
        y = x + 1;     //因为没有break,所以不可接受
    case 6:
        ……

虽然结束分支块的最常用方法是使用五个跳转语句中的一个,但编译器足够聪明,当某个结构可以使用语句列表满足“不穿过规则”时,它是可以检测到的。

int x = 5;
switch(x)
{
    case 5:
        while(true)        //满足“不穿过规则”
            DoStuff();
    default:
        throw new InvalidOperationException();
}

上述代码中测试条件为true的while循环将永远循环,并且永远不会进入下一个分支块。

十、跳转语句

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

  • break
  • continue
  • return
  • goto
  • throw

十一、break语句

除了switch语句外,break语句还可用于下列语句类型:

  • for
  • foreach
  • while
  • do

break语句导致执行跳出最内层封装语句。

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

如上,while循环如果只依靠它的测试表达式,将会是一个无限循环,它的测试表达式始终为true,但相反,在3次循环迭代之后,遇到了break语句,循环退出。

2、continue语句

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

  • while
  • do
  • for
  • foreach
for(int x = 0; x < 5; x++)
{
    if (x < 3)
        continue;
    //当x>=3时,执行下面的语句
    Console.WriteLine($"Value of x is {x}");
}

执行结果:

十三、标签语句

标签语句由一个标识符后面跟着一个冒号再跟着一条语句组成,其形式:

Identifier: Statement

标签语句的执行如同标签不存在一样,并仅执行Statement部分。

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

1、标签

标签有它们自己的声明空间,所以标签语句中的标识符可以是任何有效的标识符,包括那些可能在重叠的作用域内声明的标识符,比如局部变量或参数名。

{
    int xyz = 0;                            //变量xyz
    ……
    xyz: Console.WriteLine("No problem.");  //标签xyz
}

如上代码展示了标签的有效使用,该标签和一个局部变量有相同的标识符。

然而,也存在一些限制,该标识符不能:

  • 是关键字;
  • 在重叠范围内和另一个标签标识符相同;

2、标签语句的作用域

标签语句在其声明所在的块的外部不可见(或不可访问),标签语句的作用域为:

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

如上图左边的代码包含几个嵌套块,它们的作用域被标记了出来,在程序的作用域B中声明了两个标签语句:increment和end:

  • 图右边的阴影部分展示了该标签语句有效的代码区域;
  • 在作用域B和所有嵌套块中的代码都能看到并访问标签语句;
  • 从作用域内部任何位置,代码都能跳出到标签语句;
  • 从外部(本例中 作用域A)代码不能跳入标签语句的块;

十四、goto语句

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

它的一般形式如下,其中Identifier是标签语句的标识符:

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语句不能跳入嵌套在其所在块内部的任何块;

警告:

使用goto语句是非常不好的,因为它会导致弱结构化的、难以调试和维护的代码。

switch语句内部的goto语句

还有另外两种形式的goto语句,用在switch语句内部。这些goto语句把控制转移到switch语句内部相应命名的分支标签。但是,goto标签只能用来引用编译时常量(就像在C#7.0之前的switch语句中一样)。

goto case ConstantExpression;
goto default;
goto case PatternExpression;    //编译错误

十五、using语句

某些类型的非托管对象有数量限制或很耗费系统资源,在代码使用完它们后,尽快释放它们是非常重要的,using语句有助于简化该过程并确保这些资源被适当地处置。

资源是指实现了System.IDisposable接口的类或结构,IDisposable接口含有单独一个名称为Dispose的方法。

使用资源的阶段如上图,由以下部分组成:

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

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

注意:using语句不同于using指令(如,使用System.Math;)。

1、包装资源的使用

using语句帮助减少意外的运行时错误带来的潜在问题,它整洁地包装了资源的使用。

有两种形式的using语句,形式一如下:

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

意外的运行时错误称为异常,处理可能的异常的标准方法是把可能导致异常的代码放进一个try块中,并把任何无论有没有异常都必须执行的代码放进一个finally块中。

这种形式的using语句确实是这么做的,它执行下列任务:

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

2、using语句的示例

using System;       //using指令,不是using语句
using System.IO;    //using指令,不是using语句

namespace Ten_Sentence
{
    class _15_2UsingStatement
    {
        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);
            }
        }
    }
}

执行结果:

3、多个资源和嵌套

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

using(ResurceType 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...");
    }
}

4、using语句的另一种形式

关键字   资源
  ↓       ↓
using(Expression)
{
    EmbeddedStatement//使用资源
}

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

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

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

十六、其他语句

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值