2.2 相关知识

2.2.1 数据类型

数据是程序处理的对象,C#中数据的类型分为值类型和引用类型。具体类型划分如图2-2所示。

图2-2 C#的数据类型

C#程序设计语言提供了一系列数据类型,以便告知计算机如何对各种类型数据进行存储等操作。C#基本的数据类型、类型标识符及其取值范围见表2-1。

表2-1 基本数据类型表

本节先学习几种最常用的数据类型。

1.整型

整型是指整数类型。在C#中提供了有符号整数和无符号整数两大类型整数,两种类型中又分为字节型、短整型、整型和长整型等整数类型。

例如:int age=19;

age是int整数类型的变量,变量名称是age,存储了整数19,在内存中占用4个字节的存储空间。

2.浮点型

在C#中提供了浮点型数据类型,用来描述小数,浮点型数据类型包括单精度浮点型(类型标识符float)和双精度浮点型(类型标识符double)。

例如:double score=98.5;

score是双精度浮点型double类型的变量,存储了小数98.5,在内存中占用8个字节的存储空间。

3.字符型

C#提供的字符类型,采用Unicode字符集。字符类型的变量的定义、赋值的方法如下:

char sex=′F′;

字符类型的值,只能描述一个字符,并且必须使用单引号括起来。而对于单引号、换行符、退格符等特殊字符,C#提供了转义字符,即转变了字符本身含义,用来代表特殊字符,例如:′\n′,代表的不是字符n,而是换行符。常用的转义字符见表2-2。

表2-2 转义字符表

4.布尔型

布尔类型的变量定义、赋值的方法如下:

bool flag=False;

布尔类型,主要用来描述、存储逻辑判断结果的值。例如在表达式中判断数值变量num1和num2的大小比较结果,并使用变量flag存储比较的结果,假设num1=3,num2=5,那么num1大于num2的比较结果就是“假”。在现实中的“真”和“假”两个概念,C#中使用True和False两个布尔类型值来表示,所以flag中的值就是False,逻辑假。

5.结构类型

结构类型用于把不同类型的数据信息存储到一起。结构类型是用户自定义的数据类型,类似C语言的结构体。

结构类型的定义语法:

例如:定义一个student结构类型,用于存储学生的学号、姓名、性别、成绩4个数据项,代码片段如下:

结构类型的应用,在定义了结构类型之后,首先要定义结构类型变量,例如:定义结构类型student的变量stu,代码片段如下:

student stu;

再对变量stu的每个结构成员赋值、引用,代码片段如下:

stu.no=10003;

stu.name="王小明";

stu.sex="男";

stu.score=89.5;

2.2.2 变量

变量是内存中的一块空间,提供了可以存储信息和数据的地方,具有记忆数据的功能。变量的值是可以改变的。变量就像一个容器,比如有200ml水,需要一个水杯cup容器存放,这个水杯cup就相当于变量,水杯里存放的200ml的水,就相当于变量的值。口渴了,喝了半杯,此时cup里的值就是100ml,太渴了,一口气喝光了,cup的值就等于0ml。可见,变量中的值是可以改变的。

如果一个苹果放进竹篮子容器里,是正确的;如果200ml的水放进竹篮子容器里,使用的容器类型就不对了,同样的道理,C#要求在程序中使用任何变量之前必须先声明其数据类型,C#根据所存储的数据类型的不同,有不同类型的变量。

1.变量声明

C#语言规定,程序中所使用的变量必须遵循“先定义,后使用”的原则。变量的定义需要指出变量的数据类型和变量的名称,声明变量的语法,一般格式为:

数据类型变量名;

给变量赋值的语法,一般格式为:

变量名=值;

例如:

int length;

length=27;

当然也允许在定义变量的同时,为其赋初值(即初始化),一般格式为:

数据类型变量名[=初始值];

例如:

int length=27;

定义了一个整型变量,变量的名称是length,被赋予的初始值为整数27。

例如:

double score=98.5;

定义了双精度浮点型变量,变量名称score,为其赋的初始值为98.5。

例如:

char grade=′A′;定义了字符类型变量,变量名称为grade,赋的初始值为字符‘A’。

C#中的变量命名需要注意以下问题。

1)必须是一个合法的标识符。

2)变量名区分大小写。如:stuName和stuNAME是两个不同的变量。

3)在其作用域中是必须唯一的。在不同的作用域才允许存在相同名字的变量。

4)变量名最好有一定的含义,能够“见名知意”,以增加程序的可读性。

2.变量的作用域

每个变量都有一个相应的作用范围,也就是它可以被引用的范围,这个作用范围称为变量的作用域。当一个变量被定义的时候,它的作用域就确定了。变量的声明位置不同,其作用域也不相同。按作用域的不同,变量可以分为以下类型。

(1)成员变量

成员变量在类中声明,它的作用域是整个类。这是作用域范围最大的变量。

(2)局部变量

局部变量在方法内部或者一个块语句的内部声明。如果在一个方法内部声明,它的作用域就是整个方法;如果在一个块语句的内部声明,它的作用域就只限于这个块语句内部。块语句就是括在一对大括号“{}”内的一组代码。

(3)方法的形式参数

方法或者构造方法的参数,它的作用域是整个方法或者构造方法之内。

【例2-1】编写控制台程序,显示一杯水的变化过程。

程序运行效果图,如图2-3所示。

图2-3 【例2-1】程序运行效果图

程序源代码如下:

在例题中,定义了整型变量cup,定义的同时完成赋初值200,在程序中cup的值是变化的,是可以重新赋值的,同时通过输出语句“Console.WriteLine( );”输出变量cup的值。

2.2.3 常量

常量是在程序运行中其值保持不变的量,也称为常数。常量值当然也是有数据类型的,例如“学生信息”就是字符串常量,描述成绩等级的‘A’就是字符常量,成绩98.5就是实型常量。

常量可分为普通常量和符号常量。像‘A’、98.5、“学生信息”,这些都是普通常量。对于程序中多次使用的普通常量,为了避免重复书写导致的错误,也便于程序可读性、可维护性,C#也支持符号常量,即使用“见名知意”的符号代替这个常量值。符号常量的名字一般全部用大写字母,当然特别需要注意的是,符号常量在声明时赋值,之后是不能修改的。

符号常量的声明,一般格式为:

const数据类型符号常量名=常量值;

【例2-2】编写控制台程序,计算半径为3厘米的圆的周长和面积。

程序运行效果图,如图2-4所示。

图2-4 【例2-2】程序运行效果图

程序源代码如下:

在例题中,关键字const表明PI是一个符号常量,PI代表的常量值是3.1415926,而关键字double表明PI的数据类型为“双精度浮点型”。在程序代码中计算圆的周长、面积,使用了两次圆周率,因为在程序开始定义了符号常量PI,所以圆周率3.1415926这个常量值,在定义符号常量PI的时候只书写了一次,之后用到圆周率的地方,就可以用符号常量PI代替了,这样避免重复书写导致错误;如果之后修改程序,想对圆周率保留两位小数,只要在最初给符号常量赋值时修改为“const double PI=3.14”,在程序中出现多次的圆周率也就“一改全改”了。

2.2.4 运算符与表达式

按照对操作数的操作结果分类,运算符可以分为算术运算符、关系运算符、逻辑运算符、赋值运算符等。用运算符和括号将操作数连接起来的符合C#语法规则的式子,称为C#表达式,相应的有算术表达式、关系表达式、逻辑表达式等。运算对象可以是常量、变量、方法等。

1.算术运算符

算术运算用于完成数值计算。算术运算符的操作数必须是数值类型的常量、变量或返回值为数值类型的方法调用。算术运算符共有8个:加、减、乘、除、取余、取反、自加和自减。

(1)+:加

加号两边如果是两个数值型数据,将计算两个数据的和。

例如:Console.WriteLine(3+5);输出的结果为8,其中3和5是两个操作数,“+”是加法运算符,“3+5”是算术表达式,其计算后的输出结果是8。

需要注意:如果加号两边包含字符串,则会把两边的表达式连接成新的字符串,此时的“+”成为字符串的连接运算了。

例如:Console.WriteLine(“3”+5);输出结果为35,其中“3”是大范围(字节数)的字符串类型,5是小范围的整数类型,两种不同类型的数据进行操作时,系统就将“+”连接运算符两边的操作数统一成大范围的数据类型,小范围的整数类型5,自动向大范围的字符串类型转换,变成字符串“5”。此时“+”不是数值的加法运算符而是字符串的连接运算符了,起的作用则是将两个字符串连接成为一个字符串,即“35”。

(2)-:减,或者取反

减号“-”的作用,减号两边如果是两个数值型数据就是减法运算,计算两个数据的差。

例如:Console.WriteLine(3-5);输出的结果为-2。

如果“-”只有一个操作数,则是取反运算,运算的结果是原数据的相反值。

例如:Console.WriteLine(-(-5));输出的结果为5。即对数据-5进行取反运算-(-5),得到与-5相反的值,即5。

(3)*:乘

乘号的作用即是对*乘号两边的数据求乘积。

例如:Console.WriteLine(3*5);输出的结果为15。

(4)/:除,或者整除

除是求两个数据相除的商。如果“/”两边的运算量,不全是整数类型,有一个或两个为浮点型数据,那么运算时,系统就会将“/”两边的运算量自动向大范围、较高精度的数据类型转换,计算结果也就是高精度的数据类型了。

例如:Console.WriteLine(3/0.4);输出的结果为7.5。首先“/”两边的运算量3是整型数据(int),0.4是浮点型数据(double),两种不同类型的数据进行运算,低精度类型会自动转换为较高精度的类型。所以,int类型3会自动转换为double类型3.0,然后表达式3.0/0.4进行运算,结果为7.5。

但是,当“/”运算符两边的操作数都是整型数据,则此时“/”运算符就是整除运算符,运算结果也是整数类型,处理办法是运算结果仅保留商的整数部分,而小数部分全部舍掉。

例如:Console.WriteLine(3/4);输出的结果为0。对于常规的除法运算来说,3除以4结果应该等于0.75,而整除的运算规则只取0.75的整数部分0,小数部分全部舍掉,所以运算结果是0。

又如:Console.WriteLine(13/5);输出的结果为2。

(5)%:取余(取模)

除号“/”的作用是求两个数字相除的商,而取余运算符“%”,也称作取模运算,作用是计算两个数字相除的余数。

例如:Console.WriteLine(19/5);是计算19除以5的商,其中19和5都是整数,则做整除运算,输出结果只取除法运算结果3.8中的整数部分,所以输出结果是3。

而对于Console.WriteLine(19%5);是计算19除以5的余数(注意区分数学里的余数和小数部分两个概念),此处输出结果是4(19除以5的商是3,余数是4)。

在程序的编写中,“%”常常用来检查一个数字是否能被另一个数字整除。

例如编程判断某个数是否为偶数。我们知道偶数是2的倍数,也就是一个偶数除以2的余数是0,而奇数除以2的余数是1,程序片段如下:

int num=29;

Console.WriteLine(num%2);输出结果1,则表示num是奇数。

当然,“%”取余运算符两边的操作数必须是整数类型。

上述运算符的结合方向为“从左到右”。

(6)++和--:自加、自减

“++”自加运算符、“--”自减运算符,都是单目运算,操作数只有一个,且自加、自减的操作数只能为变量,用来完成变量的加1、减1的运算。

例如:王小明今年18岁,明年会长一岁,编程实现的代码片段如下:

int age=18;//今年王小明18岁

age=age+1;//明年,王小明的年龄是今年的年龄+1

Console.WriteLine("明年王小明{0}岁了。",age);//输出结果:明年王小明19岁了。

那么此处使用自加运算符就可以写成这样:

int age=18;//今年王小明18岁

age++;//明年,王小明的年龄是今年的年龄+1

Console.WriteLine("明年王小明{0}岁了。",age);//输出结果:明年王小明19岁了。

实质上,age++;与age=age+1;作用相同,都是变量的值在原来的基础上+1。

“--”自减运算符。与自加运算同理,age--;就等同于age=age-1;它是变量的值在原来的基础上-1。

另外,age++;与age--;也可以写作++age;或--age。

需要注意:如果与其他运算在同一语句中,++写在变量前面或后面,运算结果是不一样的,请看下例“++”写在变量之后的程序片段:

int age=18;

Console.WriteLine(age++);

程序的输出结果是18,此语句作用等同于下面两句:

Console.WriteLine(age);//先执行打印,输出结果:18

age=age+1;//再执行变量自加1,此时age=19

而如果“++”写在变量之前,如下:

int age=18;

Console.WriteLine(++age);

输出结果:19,此语句作用等同于下面两句:

age=age+1;//先执行变量自加1

Console.WriteLine(age);//后执行打印,此时age=19。运算顺序不同,所以输出的结果也不相同。

从上面例子可以看出,这两个运算符都有两种使用方式。将运算符写在变量之前,通常称为“前缀”形式,运算符写在变量之后,通常称为“后缀”形式。对于前缀形式“++age”,“先加再用”;对于后缀形式“age++”,“先用后加”。自减运算,原则相同。

说明:

1)自加自减运算的操作数只能是变量,而不能是常量或表达式。例如3++或++(a+b)都是错误的。

2)自加自减运算的结合方向是“从右到左”,其优先级别高于基本算术运算符。

2.关系运算符

关系运算也称为比较运算,用于比较两个表达式的大小。由关系运算符构成的表达式为关系表达式,C#中提供了六种关系运算符,见表2-3。

表2-3 关系运算符

说明:由关系运算符构成的表达式称为关系表达式,多用于控制结构的条件判断中。关系运算的结果是布尔类型的逻辑值True(真)或False(假)。

例如:Console.WriteLine(5<3);输出结果是:False。

3.逻辑运算

逻辑运算符用来连接多个bool类型表达式,实现多个条件的复合判断,是对True(真)或False(假)的运算。C#提供的逻辑运算符有!(逻辑非)、&&(逻辑与)、‖(逻辑或)。逻辑运算符属于二元运算符,常用来表示一些复杂的关系。表2-4列出了C#的逻辑运算符。

表2-4 逻辑运算符

例如:Console.WriteLine(3>5‖4<6);输入结果为:True,其中比较运算表达式“3>5”运算结果为False,“4<6”的运算结果为True,逻辑或“‖”运算符两边的运算量有一个为True,结果为True。而Console.WriteLine(3>5&&4<6);输出结果为:False,对于逻辑与运算“&&”,只有运算符两边都为True,结果才为True。

4.赋值运算

赋值是通过赋值运算符(=)进行的,其通用格式为:

变量名=表达式;

其功能是:首先计算赋值号“=”右边表达式的值,然后将表达式的值赋予左边的变量。右边表达式的值可以是任何类型的,但左边必须是一个明确的、已命名的变量,而且该变量的类型必须与表达式值的类型一致。

例如:

int m=3,n;//此语句定义整型变量m的同时,为其赋初始值3;同时定义整型变量n

n=m+5;//首先计算赋值号右边的表达式m+5,结果为8,然后将8赋值给左边变量n

Console.WriteLine("{0}",n);//输出结果n的值为8

在普通赋值运算符“=”之前加上其他运算符,就构成了复合赋值运算符,也称为扩展赋值运算符。

例如,加赋值“+=”,请看下面的代码片段:

int x=3;

x+=2;

Console.WriteLine("{0}",x);

此代码片段运行结果为5,其中“x+=2;”语句,先做加法再赋值,相当于“x=x+2;”语句,“=”右边,先执行“x+2”,结果是5,再执行赋值,所以x的输出结果是5。

同样地,减赋值“-=”:先执行减法,然后赋值。例如:

int x=3;

x-=2;//此处复合赋值运算语句等同于x=x-2;

Console.WriteLine("{0}",x);//输出结果是1

对于乘赋值“*=”、除赋值“/=”、取余赋值“%=”等,也都是一样的道理。

与其他运算符从左向右计算不同,赋值运算符从右向左计算。

同样地,“a*=b+5”语句先执行“a*(b+5)”,然后将计算结果赋值给a,此语句相当于“a=a*(b+5)”语句,也就是说要把复合赋值运算符右边的表达式作为一个整体来参与运算。

5.运算符的优先级

在一个表达式中往往存在多个运算符,此时要按照各个运算符的优先级及结合性进行运算。也就是说在一个表达式中,优先级高的运算符首先运算,然后是运算优先级较低的,相同优先级的运算符要按照它们的结合性来决定运算次序。运算符的优先级由高向低如下所示。

1)括号( )。优先级最高的运算符是括号( ),如同数学中常提到的,“有括号先计算括号里面的”。C#语言也是一样,如果有多层括号,要从里向外计算。例如2*(3+5)。

2)一元运算符。对于运算符两边有两个运算量(操作数)的,例如3+5、10%3等,这些称作二元运算符。只有一个运算量的运算符称作一元运算符,它们的优先级高于二元运算符。一元运算符有:++(自加)、--(自减)、!(逻辑非)和-(取反)等运算符。例如:表达式“-5*3+2”的运算结果是-13。

3)算术运算符。算术运算符内部的优先级,和算术里提到的“先乘除,后加减”一样,*(乘)、/(除)、%(取余)的优先级高于+(加)、-(减)。而对于优先级相同的运算符,则从左向右计算。

4)关系运算符。其中>(大于)、<(小于)、>=(大于等于)、<=(小于等于)优先级高于==(等于)、!=(不等于)。

5)逻辑运算符。其中&&(逻辑与)高于‖(逻辑或)。

6)赋值运算符。赋值运算符有:=、+=、-=、*=、/=、%=。赋值运算符的运算从右向左。

2.2.5 类型转换

数据类型转换是将一种类型的数据转变为另一种类型的数据。当表达式中的数据类型不一致时,就需要进行数据类型转换。类型转换的方法有两种:自动转换和显示转换。

1.自动转换

不同数据类型在程序运行时所占用的内存空间不同,因此每种数据类型所容纳的信息量也不同。当一个容纳信息量小的类型转化为一个信息量大的类型时,数据本身的信息不会丢失,这种转换是安全的,此时编译器将自动完成类型转换工作,这种转换称为自动类型转换。就好像要将容积200mL水杯里的150毫升水与容积200L水缸里50L的水混合,当然是将小容量水杯里的水倒入大容量的水缸里。同样的道理,两种不同类型的数据运算,低精度类型会自动转换为较高精度的类型。例如:3.5+8,显然数字8的精度较低(int),而3.5的精度较高(double),所以整数8会自动转换为双精度浮点类型,即转换为3.5+8.0进行运算,结果为11.5。

例如:double num=2;此处2是整型数据,精度显然低于变量num的双精度浮点类型的精度,所以2会自动转换为2.0然后赋值给变量num。

例如:int num=3.0;变量num定义为int整型,精度低于3.0,变量的值可以改变,但变量的类型是定义时已经固定的,所以这条语句编译时会出错,“无法将类型‘double’隐式转换为‘int’。存在一个显式转换(是否缺少强制转换?)”,此处提示的隐式转换,即是自动转换,从而程序无法继续运行。

2.显式转换

显示转换是通过程序代码,调用专门的转换方法将一种数据类型显式地(强制地)转换成另一种数据类型。

(1)强制类型转换

高精度数据转换成低精度数据时,需要用到强制类型转换。即把一个容量较大的数据类型向一个容量较小的数据类型转换时,可能面临信息丢失的危险,就像要把大水缸里的水倒进水杯里,可能会溢出一样,此时必须使用强制类型转换,将高精度数据强制转换成低精度数据。强制类型转换的一般语法为:

(类型)表达式(或变量名)

例如:int num=3.0;对于这样无法自动转换为我们需要的类型,可以用强制类型转换,这个语句可以这样完成:int num=(int)3.0;数字3.0前面的(int)表示转换的目标类型为int,3.0会被强制转换为3。

需要注意:double型数据强制转换为int型数据将失去小数部分,例如(int)5.6,得到的结果是5。

(2)ToString( )方法

ToString( )方法的作用是将非字符串类型数据转换成字符串类型数据。一般语法为:

变量.ToString( );

【例2-3】编写控制台程序,将年、月、日按8位形式输出日期。

程序运行效果图,如图2-5所示。

图2-5 【例2-3】程序运行效果图

程序源代码如下:

在【例2-3】中,定义了整型变量y、m、d,分别存储日期中的年、月、日三个值,再分别使用方法ToString( )将整型数据转换成字符串类型数据,使用字符串的连接“+”运算符将3个字符串连接成一个整串构成8位的日期“20161217”。如果此处不将整型数据转换成字符串类型,而进行“+”运算,则构成了算术加法运算,结果将为2045。

(3)Parse( )方法

Parse( )方法可以将字符串类型转换成数值类型,一般语法为:

数值类型名称.Parse(字符串表达式);

其中数值类型名称如int、float、double等,字符串表达式的值则一定要与Parse前面的数值类型格式一致。例如:int.Parse(“35”);方法Parse( )前面的数值类型是整型,则要转换的字符串类型数据也一定是对应的数值格式,如果写成int.Parse(“35.6”)则运行时会报错,程序无法运行。对于字符串“35.6”如果需要转换成数值型数据,正确写法是double.Parse(“35.6”),此处要前后一致。

【例2-4】编写控制台程序,模拟简单的加法计算器,当用户输入要计算的两个加数,程序计算两个加数的和,并输出结果。程序运行效果图,如图2-6所示。

图2-6 【例2-4】程序运行效果图

程序源代码如下:

在【例2-4】中,加法计算器运行时需要用户输入要计算的两个加数。控制台输入方法Console.ReadLine( )读入的数据是字符串类型,而两个字符串数据要进行加法运算,显然需要将两个字符串分别转换成两个数值型数据,再使用加法运算符“+”进行加法计算,从而得到两数之和。

(4)Convert类

Convert类中,提供了很多常用的方法,可以灵活地对各种类型数据进行数据类型转换。表2-5列出了Convert类常用的几种类型转换方法和方法说明。

表2-5 Convert类的常用方法说明

【例2-5】编写控制台程序,计算王晓明同学语文、数学两门科目的平均成绩,并输出结果。程序运行效果图,如图2-7所示。

图2-7 【例2-5】程序运行效果图

程序源代码如下:

在【例2-5】中,应用了Convert.ToInt32( )方法将控制台输入的字符串类型的学生成绩转换成了32位有符号整数类型,从而数值类型数据可以进行加法、除法等算术运算。

2.2.6 流程控制语句

C#语言通过使用流程控制语句来改变程序流的执行,从而完成程序状态的改变。C#的流程控制语句有顺序、分支、循环和跳转。分支语句的作用是根据条件表达式的结果或状态变量的值来决定程序所执行的分支路径;循环语句是能够控制一个或一个以上的语句,使其重复执行,从而形成循环;跳转语句作用允许程序以非线性的方式执行。C#中的控制语句包括:

●分支语句:if…else、switch;

●循环语句:while、do…while、for;

●跳转语句:break、continue。

C#语言就是通过这些控制语句来控制程序流的执行,从而形成了程序的三种基本结构,即顺序结构、分支结构和循环结构。顺序结构就是按着程序中语句书写的自然顺序从上到下来逐条执行程序语句,这是最简单的一种程序结构,不需要流程控制语句进行控制。

1.分支语句

分支语句也称作选择语句,就像走到分岔路口,需要选择方向,编写程序也会遇到判断和分支。C#的分支语句有两种:if语句和switch语句。这两种语句都是在程序流执行到某一位置时,根据当时条件表达式的结果或状态变量的值来选择程序流接下来要执行的语句块。在C#中,这个结构称作分支结构(或者条件结构、选择结构)。

(1)基本if语句(单分支语句)

一般语法:

if(条件){代码块;}

程序执行的流程图,如图2-8所示。

图2-8 基本if语句流程图

如果“条件”为“真”,则执行“代码块”;否则不执行。

【例2-6】编写控制台程序,录入学生成绩,如果成绩小于60分,则通知学生按时参加补考。程序运行效果图,如图2-9所示。

图2-9 【例2-6】程序运行效果图

程序源代码如下:

在【例2-6】中,程序在“score<60”这个步骤出现了分支,“score<60”被称为条件判断(bool类型),当判断结果为True时,执行下面的代码块,输出提示;当判断为False时,则不执行代码块,不输出任何内容。在程序运行过程中,用户输入的成绩score是58,显然满足“score<60”的条件,所以我们看到了输出提示。

其中,代码块也称作语句块,当语句块中的语句多于一条时,必须用“{”和“}”括起来。

(2)if…else语句(双分支语句)

一般语法:

if(条件){代码块1;}

else{代码块2;}

程序执行的流程图,如图2-10所示。

图2-10 if…else语句流程图

如果“条件”为“真”,则执行“代码块1”;否则执行“代码块2”。

【例2-7】编写控制台程序,录入学生成绩,如果成绩大于等于60分,则通过考核;成绩小于60,则通知学生按时参加补考。程序运行效果图,如图2-11所示。

图2-11 【例2-7】程序运行效果图

程序源代码如下:

在【例2-7】中,程序在“score>=60”这个步骤出现了分支,“score>=60”为判断条件(bool类型),当判断结果为True时,执行下面的代码块1,输出“通过考核”提示;当判断为False时,则执行代码块2,输出“请参加补考”提示。

每个if…else结构都包含一个条件和两个分支,称作双分支结构,而程序会根据条件的真与假,选择执行其中的某一个分支。条件必须为bool类型的表达式。

(3)嵌套的if语句

在程序开发中,往往需要先判断一个条件是否成立,再判断另一个条件。也就是在分支结构中的代码块部分又是一个分支结构,在前面提到的单分支或者双分支结构中,有一个代码块嵌套了任一种分支结构,都构成了分支嵌套。例如如下结构:

【例2-8】编写控制台程序,进行用户登录验证,先验证账号是否为“admin”,如果不是则提示错误;如果是,则验证密码是否为“123456”。程序运行效果图,如图2-12所示。

图2-12 【例2-8】程序运行效果图

程序源代码如下:

在【例2-8】中,先判断外层的if(name=="admin")条件,如果结果为False,就会输出“用户名错误!”;如果结果为True,则判断内层的if(password=="123456")条件。实质上,第二个条件判断结构{if(password=="123456")…else…}就是第一个条件判断结构if(name=="admin"){代码块1}else{代码块2}中的代码块1,从而构成了if语句的嵌套结构。

(4)多重分支if结构

如果有多个条件,其中只有一个成立,解决这样多重分支结构的问题时,可以使用if语句的嵌套结构,但是如果嵌套的层数太多,会使程序变得复杂和难以理解,而且容易产生错误。对于这种问题,可以使用多重分支结构解决。

一般语法:

if(条件1)

{代码块1;}

else if(条件2)

{代码块2;}

else if(条件3)

{代码块3;}

else

{代码块n+1};

条件表达式从上到下依次求值,一旦找到结果为真的条件,就执行与该子句相关的代码块,该结构后面的部分就被忽略了。结构中最后的else语句经常被作为默认的条件,即如果所有的条件都为假,就执行最后的else代码块n+1。如果没有最后的else子句,而且所有其他的条件都为假,那么程序就不做任何动作。

【例2-9】编写控制台程序,根据输入的百分制成绩,评定学生的考核等级。大于或等于90分为A等级;大于或等于75分,小于90为B等级;大于或等于60分,小于75分为C等级;小于60分为D等级。成绩划分的数轴,如图2-13所示。

图2-13 【例2-9】成绩划分

程序运行效果图,如图2-14所示。

图2-14 【例2-9】程序运行效果图

程序源代码如下:

在【例2-9】中,程序运行过程中,用户输入成绩78分,78不满足if(score>=90)的条件1,所以不执行代码块1;继续判断if(score>=75)条件2,显然“78>=75”的表达式结果为真,于是执行代码块2,从而grade为B等级,分支结构结束。

(5)switch语句

解决多重分支结构的问题时,除了使用if…else之外,C#还提供了一种专门用于多重分支结构的语句——switch语句,以便实现从多条分支中选择一条执行。switch语句的一般语法如下:

switch结构中的“表达式”的值,只能是3种类型:整型(如int)、字符型(char)、字符串类型(string)。switch语句的执行过程是:首先“表达式”的值与每一个case后面的常量表达式进行等值比较,如果相等,就执行对应的代码块。执行完该分支以后,break关键字会使switch结构中止,不会再判断后面的常量表达式。如果“表达式”的值与所有的常量表达式的值都不相同,则执行default后面的分支。

当然,default子句是可选的。如果没有匹配的case子句,也没有default子句,则不执行任何语句。

图2-15 【例2-10】程序运行效果图

【例2-10】使用switch语句重写前面的例题。根据输入的百分制成绩,评定学生的考核等级。90~100分为A等级;75~90为B等级;60~75分为C等级;小于60分为D等级。

程序运行效果图,如图2-15所示。

程序源代码如下:

需要注意:switch语句仅能用于测试相等的情况,即switch语句只能在各个case的常量值中寻找与表达式值匹配的值。而if语句可计算任何类型的布尔表达式。在同一个switch语句中没有两个相同的case常量值。switch语句也可以嵌套,外部switch语句中的case常量可以和内部switch语句中的case常量相同,而不会产生冲突。switch语句通常比一系列嵌套if语句更有效,可读性强。

2.循环语句

循环语句的作用是重复执行一段程序代码,直到循环条件不再成立为止。重复执行的语句称为循环体。C#提供的循环语句有while语句、do-while语句和for语句三种,这些语句创造了通常所说的循环结构程序。

(1)while语句

while语句是C#最基本的循环语句。它的一般语法如下:

while(循环判断条件)

循环操作

“循环判断条件”是循环控制表达式,是循环的判断条件,它可以是任何布尔表达式。循环体的循环操作语句如果多于一条,需要用大括号括起来。

程序执行的流程图,如图2-16所示。

图2-16 循环语句流程图

while语句执行步骤如下。

1)计算“循环判断条件”布尔表达式的值。

2)如果值为“真”,循环体的循环操作就被执行一次,然后回到步骤1)。若值为“假”,则不执行循环体的循环操作,直接执行步骤3)。

3)执行while循环结构后面的语句行。

简单地说,循环是由循环体(需要重复执行的、循环操作的语句)和循环判断条件组成的。运行时,先判断循环条件,若条件为True,就执行循环体一次,然后判断循环条件……当条件为False时,结束循环。

【例2-11】编写控制台程序,在屏幕上输出打印1~10的整数。

程序运行效果图,如图2-17所示。

图2-17 【例2-11】程序运行效果图

程序源代码如下:

在【例2-11】中,要打印1~10的整数,可以写10条输出语句,而使用循环结构控制程序流程,程序会更简洁,提高效率。使用while语句控制循环体的重复次数,首先判断“i<=10”,条件为真时,执行循环体:输出当前i的值和i++语句;之后,执行完本次循环体后再次判断条件“i<=10”,若条件依然为真,则再次继续执行循环体……若条件为假,结束整个循环结构。

(2)for语句

C#中还有一种非常有用的for循环语句,特别适合于“已知循环次数”的循环。for语句的一般语法如下:

for循环的执行过程如下。

1)当循环启动时,先执行“变量声明初始化”部分,为循环控制变量设置初始值。重要的是这个初始化表达式仅在循环开始时被执行一次。

2)计算“循环判断条件”表达式的值。表达式必须是布尔表达式。如果表达式值为“真”,则执行一次循环体,然后执行步骤3);如果为“假”,则循环终止,转去执行循环体后面的语句行。

3)执行“变量变化”部分,通常是增加或减少循环控制变量的值,以便使条件表达式的值发生变化,并趋于循环结束。“变量变化”部分执行完,一次循环结束,然后转回去执行2)。这个过程不断重复直到条件表达式值变为“假”。

需要注意:for循环语句中for(;;)中的两个分号是不能缺少的,即便没有相应的表达式,分号也必不可少。

【例2-12】使用for语句实现前面的例题,在屏幕上输出打印1~10的整数。

程序运行效果图,如图2-18所示。

图2-18 【例2-12】程序运行效果图

程序源代码如下:

由【例2-12】可以看到,while循环语句中的变量声明、循环判断条件、变量变化等元素在for循环语句中一个也不缺,但是for循环把这些跟循环次数有关的元素都放在for(;;)中,使得循环体{}中的循环操作更加纯粹,程序结构更加清晰。

(3)do…while语句

do…while语句也是常用的一种循环控制结构。一般语法如下:

do…while循环是先执行一次循环体,然后才判断位于while后面的循环判断条件表达式的值,如果此时条件表达式的值为“真”,则开始下一次循环;否则循环结束。

【例2-13】使用do…while语句实现前面例题,在屏幕上输出打印1~10的整数。

程序运行效果图,如图2-19所示。

图2-19 【例2-13】程序运行效果图

程序源代码如下:

由【例2-13】可以看出,在do…while循环语句中,在第一次执行循环体时没有进行循环条件判断,也就是说会无条件地执行一次循环体,此后的逻辑顺序就与while循环相同了,先判断条件,结果为True再执行循环体一次,直到结果为False,结束整个循环结构。即使循环条件始终为False,例如,在定义变量并赋初始值时“int i=11;”,最初i的值就不满足“i<=10”的循环判断条件,但由于do…while循环第一次执行循环体不判断条件,所以循环体也会执行一次。此时将得到输出结果11。

3.跳转语句

跳转语句能够改变程序执行的流程。C#提供的跳转语句有continue、break等。

(1)continue语句

循环中应用continue语句,可以中止一次循环,直接进入下一次。也就是当程序执行到“continue;”的时候,会立即停止本次循环体,直接进入下一次循环。一般语法如下:

continue;

【例2-14】编写控制台应用程序,C#集中训练学习100天,每天吃饭、睡觉、学习,其中每10天休息一次。在屏幕上输出学生C#集中训练学习过程。

程序运行效果图,如图2-20所示。

图2-20 【例2-14】程序运行效果图

程序源代码如下:

在【例2-14】中,循环体内部当变量i的值等于10时,也就是第10天,满足条件“if(i%10==0)”所以输出“第10天,休息。”而后执行到“continue;”语句的时候,立即停止本次循环体的继续执行,所以,在屏幕上没有“第10天,吃饭,睡觉,学习。”的输出,而是直接进入下一次循环(即第11次循环)。当然之后的i=20、30、40……满足“if(i%10==0)”条件的皆是如此。一般使用continue关键字,在循环中剔除一些特殊的数据。

(2)break语句

前面学习switch语句时,曾经遇到过break语句。break在switch语句中的作用是“跳出switch结构”。

break语句还可以用在循环中,作用是“结束循环”。一般语法如下:

break;

【例2-15】编写控制台应用程序,C#集中训练学习100天,每天吃饭、睡觉、学习,其中每10天休息一次。每学习一天获得2学分,得到100分则学分修满,学习结束。在屏幕上输出学生C#集中训练学习过程。

程序运行效果图,如图2-21所示。

图2-21 【例2-15】程序运行效果图

程序源代码如下:

在【例2-15】中,循环体内部当满足“if(count>=100)”条件时,执行到if分支代码块中的break;语句,循环结构结束。尽管此时i=55仍然满足for语句的循环条件“i<=100”,但循环都被强制性终止,程序流程从循环体后面的语句继续执行。

2.2.7 异常处理

异常,即有异于常态,和正常情况不一样,在程序中是指运行过程中有错误出现,阻止当前方法、程序继续正常执行。

异常处理是对程序运行时出现的非正常情况进行处理。异常处理可提高程序的健壮性和容错性。

1.异常的概念

程序中的错误一般分为三类:编译错误、运行错误和逻辑错误。编译错误是因为程序存在语法问题,未能通过编译而产生的,由编译系统负责检测和报告,没有编译错误是一个程序运行的基本条件。逻辑错误是指程序不能按照预期的方案执行,它是编译系统无法检测的,需要人工对运行结果及程序逻辑进行分析,从中找出错误的原因。运行错误是程序运行过程中产生的错误,这类错误可能来自程序员没有预料到的各种情况,或者超出程序员控制的各种因素,如除数为0、数组下标越界、不能打开指定的文件等,这类错误称为异常(Exception),也叫作例外。C#提供了有效的异常处理机制,以保证程序的安全性。

【例2-16】编写控制台应用程序,计算两个整数相除的商,当用户输入的除数为0时,程序发生异常。程序运行效果图,如图2-22所示。

图2-22 【例2-16】程序运行效果图

程序源代码如下:

在【例2-16】中,程序代码执行到“double result=a/b;”语句时,因为除数b的值为0,我们知道算术除法运算中,0不能做除数,所以在程序运行过程中产生了异常,程序不能正常结束。

2.异常处理

异常处理是处理程序中发生意外情况的机制,可以防止程序进入非正常状态,并可根据不同类型的错误来执行不同的处理方法。

异常处理包括4个关键字:try、catch、throw和finally,表2-6列出了几个关键字的作用说明。

表2-6 异常处理关键字说明

(1)使用try…catch…语句捕获异常

捕获异常的try…catch…语句的语法格式为:

将可能产生异常的代码放置在try语句的代码块内,catch子句位于try块后,其中包含了处理异常的代码块。一个try语句可以有多条catch语句。当位于try语句中的代码块运行时产生异常,系统就会按顺序查找能处理这种异常的catch子句,并使程序流程转到该catch块中,进行异常处理。在这种情况下catch子句的顺序很重要,因为程序会按顺序检查catch子句。将先捕获特定程度较高的异常,而不是特定程度较小的异常。如果对catch块进行排序,使程序永远不能达到后面的块,编译器将产生错误。

【例2-17】编写控制台程序,计算两个整数相除的商,并输出结果。程序运行效果图,如图2-23所示。

图2-23 【例2-17】程序运行效果图

程序源代码如下:

在【例2-17】中,将程序运行过程中可能会出错的语句或者全部程序功能语句都放在try{}语句块中,在try{}块后紧跟着一个或者多个catch语句,用于处理各种类型的异常发生,在例题中使用了三个catch语句:第一个ArithmeticException类,用于处理与算术有关的异常类型,解决除数为0的异常;第二个catch语句FormatException类,用于处理参数格式错误的异常,解决用户输入的数据类型是非整数类型时的异常;第三个Exception类,是所有异常类的基类(父类),特定程度最低。

(2)使用try…finally…语句

使用finally语句可以构成try…finally…结构或者try…catch…finally结构。对于catch语句块,如果程序运行过程中没有发生任何类型的异常,将不执行catch语句,而finally语句,不管程序是否发生了异常,都将执行finally块中的语句。因此,可以将清除资源等操作放在finally语句块中处理。

图2-24 【例2-18】程序运行效果图

【例2-18】编写控制台程序,计算两个整数相除的商,并输出结果。程序运行效果图,如图2-24所示。

程序源代码如下:

(3)使用throw语句抛出异常

尽管C#提供了相当多的异常类,系统异常并不一定总能捕获程序中发生的所有错误,当用户遇到了系统预定义的异常类不能描述的问题时,还需要创建自定义的异常。

图2-25 【例2-19】程序运行效果图

【例2-19】编写控制台程序,限制用户输入1~10的整数,当输入当数据超出范围时,进行异常处理,提示用户输入的数据不在范围内。程序运行效果图,如图2-25所示。

程序源代码如下:

在【例2-19】中,自定义了异常类MyException,它继承了ApplicationException类,第一个catch语句catch(MyException me)用于捕获自定义异常;第二个catch(Exception e)语句用于捕获一般异常,如果异常被第一个catch捕获,那么第二个catch将不会执行。程序在运行过程中不论是否发生异常,都直接执行finally{}块中的语句。