一、什么是语句
二、表达式语句
三、控制流语句
四、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语句已经释放了它的非托管资源之后使用该资源,这可能会导致状态不一致,因此它提供了较少的保护,不推荐使用。