3.2 基本语法
前面为大家介绍了C#语言的代码结构,从本节起将进入C#基本语法的学习,为后面的ASP.NET Web程序的开发打好扎实的基础。
3.2.1 数据类型
C#中数据类型可以分为“值类型”和“引用类型”,值类型包含数值类型、布尔类型、字符类型、结构类型和枚举类型;引用类型包含类类型、对象类型、字符串类型、数组类型、接口类型和代理类型等。
1.值类型
值类型是一种由类型的实际值表示的数据类型,即是源代码中值类型的变量对应到栈内存中一定大小的存储空间,该空间内直接存储所包含的值,其值就代表数据本身。由于编译器编译后将源代码中的值类型变量直接对应到唯一的存储空间上,直接访问该存储空间,故值类型的数据具有较快地存取速度。
(1)数值类型
主要包括整数、浮点数和小数,这些均属于简单类型。简单类型都是.NET Framework中定义的标准类的别名,都隐式地从object类继承而来。所有类型都隐含地声明了一个公共的无参数的构造函数,称为“默认构造函数”。默认构造函数返回一个初始值为零的实例。
①整数类型
C#中支持8种整型sbyte、short、byte、ushort、int、uint、long、ulong。这8种类型通过其占用存储空间的大小以及是否有符号来存储不同极值范围的数据,根据实际应用的需要,选择不同的整数类型。8种整数类型的比较如表3-1所示。
表3-1 整数类型
(续表)
②浮点数类型
C#支持两种浮点数类型:float和double。两种数据类型的比较如表3-2所示。
表3-2 浮点类型比较
如同整数类型一样,浮点类型的差别在于取值范围和精度不同。计算机处理浮点数的速度要远远低于对整数的处理速度。在对精度要求不是很高的浮点数计算中,可以采用float型,而采用double型获得的结果将更为精确。当然,如果在程序中大量地使用双精度浮点数,将会占用更多的内存单元,对性能的影响较大。
③小数类型
C#还专门定义了一种十进制的类型(decimal),称为“小数类型”。小数类型在所有数值类型中精度最高,有128位,一般做精度要求高的金融和货币的计算。decimal类型对应于.NET Framework中定义的System.Decimal类。取值范围大约为1.0×10-28到7.9×1028,有28~29位的有效数字。
decimal类型的赋值和定义如下所示:
代码说明:末尾m代表该数值为decimal类型,如果没有m将被编译器默认为double类型的88.8。
decimal类型不支持有符号零、无穷大和NaN。一个十进制数由96位整数和十位幂表示。小数类型较浮点类型而言,具有更大的精确度,但是数值范围相对小了很多。将浮点类型的数向小数类型的数转化时会产生溢出错误,将小数类型的数向浮点类型的数转化时会造成精确度的损失。因此,两种类型不存在隐式或显示转换。
(2)布尔类型
布尔类型是用来表示“真”和“假”两个概念的。虽然看起来很简单,但实际应用却非常广泛。布尔类型变量只有两种取值“真”和“假”。在C#中,分别采用true和false两个值来表示。
在C和C++语言中,用0来表示“假”值,其他任何非0的式子都可以表示“真”这样一个逻辑值。这种表达方式在C#中是非法的,在C#中,true值不能被其他任何非零值所代替。且在其他数据类型和布尔类型之间不存在任何转换,C++中常见的将整数类型转换成布尔类型是非法的,如下所示情形:
(3)结构类型
结构类型通常是一组相关的信息组合成的单一实体。其中的每个信息称为它的一个成员。结构类型可以用来声明构造函数、常数、字段、方法、属性、索引、操作符和嵌套类型。结构类型通常用于表示较为简单或者较少的数据,其实际应用意义在于使用结构类型可以节省使用类的内存的占用,因为结构类型没有如同类对象所需的大量额外的引用。下面的代码定义了一个地址的简单数据结构:
上面的代码中第1行使用关键字struct指明这里将要定义一个用户自定义结构,对于一个记录地址的结构,国家、身份、城市和街道是必不可少的,这里在第2~5行定义了这些信息。在使用这个结构时,可以根据需要增加相关的信息。
(4)枚举类型
枚举类型(enum)是由一组特定的常量构成的一种数据结构,系统把相同类型、表达固定含义的一组数据作为一个集合放到一起形成新的数据类型,比如一周分为7天可以放到一起作为新的数据类型来描述星期,代码如下所示:
上面的代码中第1行中enum是定义枚举类型的关键字,WeekDay是枚举类型的名字,“{}”中的是枚举元素,第2行定义了枚举元素,用逗号分隔。这样一周7天的集合就构成了一个枚举类型,它们都是枚举类型的组成元素。
结构是由不同类型数据组成的一组新的数据类型,结构类型的变量值是由各个成员的值组合而成的,而枚举则不同,枚举类型的变量在某一时刻只能取枚举中某一个元素的值,如WeekDay这个表示“星期”的枚举类型的变量,它的值要么是Sunday,要么是Monday或其他的星期元素,但它在一个时刻只能代表具体的某一天,不能既是星期二,又是星期三,也不能是枚举集合以外的其他元素,比如yesterday、tomorrow等。
按照系统的默认,枚举中的每个元素类型都是int型,且第一个元素缺省时的值为0,它后面每一个连续的元素值按加1递增。在枚举中,也可以给元素直接赋值,如下面的代码把星期天的值设为1,其后的元素Monday、Tuesday等的值分别为2、3等,依次递增。
上面的代码对星期分别进行了赋值,要注意的是为枚举类型的元素所赋值的类型限于long、int、short和byte等整数类型。
(5)字符类型
字符类型包括数字字符、英文字母、表达式符号等,C#提供的字符类型按照国际公认的标准,采用Unicode字符集。一个Unicode的标准字符长度为16位,用它可以来表示世界上大多种语言。字符类型变量赋值形式有以下三种:
在C和C++中,字符型变量的值是该变量所代表的ASCII码,字符型变量的值实质是按整数进行存储的,可以对字符型变量使用整数赋值和运算,例如下面的代码:
C#中不允许这种直接的赋值,但可以通过显示类型转换来完成,如下面的代码:
和C、C++中一样,在C#中仍然存在着转意字符,用来在程序中指代特殊的控制字符。C#中的转意字符如表3-3所示。
表3-3 转意字符
(续表)
2.引用类型
C#除支持值类型外还支持引用类型。一个具有引用类型(reference type)的数据并不驻留在栈内存中,而是存储于堆内存中。即是在堆内存中分配内存空间直接存储所包含的值,而在栈内存中存放定位到存储具体值的索引。当访问一个具有引用类型的数据时,需要到栈内存中检查变量的内容,而该内容指向堆中的一个实际数据。引用类型包括字符串、数组、类和对象、接口、代理等,本节介绍字符串和数组,其余类型在后面章节介绍。
(1)字符串类型
字符串实际上是Unicode字符的连续集合,通常用于表示文本,而String是表示字符串的System.Char对象的连续集合。在C#中提供了对字符串(string)类型的强大支持,可以对字符串进行各种的操作。string类型对应于.NET Framework中定义的System.String类,System.String类是直接从object派生的,并且是final类,不能从它再派生其他类。
字符串值使用双引号表示,例如"China","中国"等,而字符型使用单引号表示,这点读者需要注意区分。下面是几个关于字符串操作的代码:
上面的代码中第1行和第2行直接把字符串赋值给字符串变量word1和word2,第3行语句把两个字符串进行了合并。第4行用于获得字符串中某个字符值,字符串中第一个字符的位置是0,第二个字符的位置是1,依此类推。
(2)数组
数组是包含若干个相同类型数据的集合,数组的数据类型可以是任何类型。数组可以是一维的,也可以是多维的(常用的是二维和三维数组)。
①一维数组
数组的维数决定了相关数组元素的下标数,一维数组只有一个下标。一维数组通过声明方式如下:
其中,“数组类型”是数组的基本类型,一个数组只能有一个数据类型。数组的数据类型可以是任何类型,包括前面介绍的枚举和结构类型。“[]”是必须的否则将成为定义变量了。“数组名”定义的数组名字,相当于变量名。
数组声明以后,就可以对数组进行初始化了,数组必须在访问之前初始化。数组是引用类型,所以声明一个数组变量只是为对此数组的引用设置了空间。数组实例的实际创建是通过数组初始化程序实现的,数组的初始化有两种方式:第一种是在声明数组时进行初始化;第二种是使用new关键字进行初始化。
使用第一种方法初始化是在声明数组的时候,提供一个用逗号分隔开的元素值列表,该列表放在花括号中,例如:
上面的代码声明了一个整数类型的数组Array,其中共有4个元素,每个元素都是整数值。
第二种是使用关键字new为数组申请一块内存空间,然后直接初始化数组的所有元素。例如:
上面的代码通过new关键字声明了一个整数类型的数组Array,其中有4个元素,每个元素都是整数值。
数组中的所有元素值都可以通过数组名和下标来访问,数组名后面的方括号中指定下标(制定要访问的第几个元素),就可以访问该数组中的各个成员。数组的第一个元素的下标是0,第二个元素的下标是1,依此类推。下面通过一个例子来做进一步的说明:
上面的代码中,在第1行定义并初始化了一个有4个元素的数组Array,第2行使用Array[3]访问该数组的第4个元素。
②多维数组
多维数组和一维数组有很多相似的地方,下面介绍多维数组的声明,初始化和访问方法。多维数组有多个下标,例如二维数组和三维数组声明的语法分别为:
以上代码第1行声明了一个二维数组。第2行声明了一个三维数组,区别在于“[]”中逗号的数量。
更多维数的数组声明则需要更多的逗号。多维数组的初始化方法也和一维数组相似,可以在声明的时候初始化,也可以使用new关键字进行初始化。下面的代码声明并初始化了一个3×2的二维数组,相当于一个三行两列的矩阵。
以上代码初始化时数组的每一行值都使用“{}”括号包括起来,行与行间用逗号分隔。Array数组的元素安排如表3-4所示。
表3-4 Array数组的元素
要访问多维数组中的每个元素,只需指定它们的下标,并用逗号分隔开,例如访问Array数组第二行中的第1列数组元素(其值为3)的代码如下所示。
另外,C#中还支持“不规则”的数组,或者称为“数组的数组”,下面的代码就演示了一个不规则数值的声明和初始化过程。
上面代码第1行中,定义了一个名为Array的数组,它是一个由int数组成的数组,或者说是一个一维int[]类型的数组。这些int[]变量中的每一个都可以独自被初始化,同时允许数组有一个不规则形状。第2行至第4行代码中每个int[]数组定义了不同的长度,其中,第2行定义数组的第一个元素,该元素是一个一维数组,长度为3,第3行定义的一维数组的长度为6,第4行定义的一维数组的长度为8。
【实例3-2】数组的使用
本例通过控制台接受由用户输入的一个字符串,要求统计输出该字符串中每个字母(不区分大小写)出现的次数并将结果显示在控制台屏幕上。
01 启动Visual Studio 2012,执行“文件”︱“新建项目”命令,在弹出的如图3-7所示“新建项目”对话框中先选择“已安装”|“模板”|Visual C#节点下的Windows模板,然后选择“控制台应用程序”。在“名称”文本框和“解决方案名称”文本框中输入“实例3-2”,然后在“位置”文本框中输入文件路径,最后单击“确定”按钮。
图3-7 “新建项目”对话框
02 此时,在解决方案资源管理器中生成名为“实例3-2”的项目,如图3-8所示。
图3-8 生成项目
03 单击实例3-2目录下的Program.cs文件,在该文件中编写如下逻辑代码:
上面的代码中第1行定义了一个Program类。第2行定义了一个Main方法,这是程序执行的入口。第3行声明了一个包含26个元素的int类型数组CharNum,用于26个字母计数。第8行到第9行通过for循环给CharNums数组中的每个元素赋值为0。第12行接受用户输入。第15行到第21行通过for循环从用户输入的字符串中一个一个地取字符,判断这个字符是哪个字符,然后将数组对应元素的值加1,分别进行统计。第22行到第25行将数组中的元素和统计数量输出到控制台显示。
04 选择主菜单上“调试”|“开始执行(不调试)”或者直接使用快捷键Ctrl+F5运行程序,运行的效果如图3-9所示。
图3-9 运行结果
3.2.2 变量和常量
应用程序运行过程中可能需要处理多项数据,对于这些需要处理的数据项,按其取值是否可改变又分为常量和变量两种。
1.变量
变量是指在程序的运行过程中其值可以被改变的量,变量的类型可以是任何一种C#的数据类型。所有值类型的变量具有实际存在于内存中的值,也就是说当将一个值赋给变量时执行的是值拷贝操作。变量的定义格式为:
或者
上面的第一个定义只是声明了一个变量,并没有对变量进行赋值,此时变量使用默认值。第一个声明定义变量的同时对变量进行了初始化,变量值应该和变量数据类型一致。
在C#中命名一个变量应遵循如下规范:
● 变量名必须以字母开头;
● 变量名只能由字母、数字和下划线组成,而不能包含空格、标点符号、运算符等其他符号;
● 变量名不能与C#中的关键字名称相同;
● 变量名不能与C#的库函数名称相同。
下面代码是几个对变量的使用:
上面的代码中第1行的代码声明了一个整数类型的变量num,并对其赋值为200。第2行的代码对上面定义的整形变量num进行修改,将其值改为200。第3行当定义多个同类型的变量时,可以在一行进行声明,各个变量间使用逗号分隔。第5行同时对5个变量进行了赋值。
2.常量
常量是值在程序的运行过程中其值不能被改变的量。常量的类型也可以是任何一种C#的数据类型。常量的定义格式为:
上面的const关键字表示声明一个常量,“常量名”就是标识符,用于唯一的标识该常量。常量名要有代表意义,不能过于简练或者复杂。常量的声明都要使用标识符,其命名规则和变量相同。
“常量值”的类型要和常量数据类型一致,如果定义的是字符串型,“常量值”就应该是字符串类型,否则会发生错误。例如以下代码:
上面的代码中,第1行到第3行定义了三个int类型的常量,第4行和第5行定义了除法的计算。其中一旦用户在后面的代码中试图改变这5个常量的值,则编译器会发现这个错误导致代码无法编译通过。
3.隐形局部变量
隐型局部变量使用关键字var,可以在声明变量时不必声明变量类型。通过本地类型推断功能,根据表达式对变量的赋值来判断变量的类型,这样可以保护类型安全,而且也可以实现更为自由的编码。依据这个本地类型推断功能,使用var定义的变量在编译期间就推断出变量的类型,而在编译后的IL代码中就会包含推断出的类型,这样可以保证类型安全。
使用隐式类型的变量声明时,需要注意以下限制:
● 只有在同一语句中声明和初始化局部变量时,才能使用var;不能将该变量初始化为null。
● 不能将var用于类范围的域。
● 由var声明的变量不能用在初始化表达式中。换句话说,var v = v++;会产生编译时错误。
● 不能在同一语句中初始化多个隐式类型的变量。
● 如果一个名为var的类型位于范围中,则当读者尝试用var关键字初始化局部变量时,将收到编译时错误。
下面代码演示了使用var声明局部变量的各种方式。
上面的代码总第1行定义了变量i被当做一个整数,第2行定义的变量j被当做浮点数,第3行定义了一个变量k被当做了字符串。第4行到第6行都违反了隐式类型的变量声明的规则。
3.2.3 表达式和运算符
表达式和运算符是应用程序开发中最基本也是最重要的一个部分,表达式和运算符组成一个基本语句。表达式是运算符和操作符的序列。运算符是表示各种不同运算的符号,包括实际中的加减乘除,它告诉编译器在语句中实际发生的操作,而操作数既操作执行的对象。运算符和操作数组成完整的表达式。
C#中的运算符非常多,从操作数上划分运算符大致分为三类。
● 一元运算符:处理一个操作数,只有少数几个一元运算符。
● 二元运算符:处理两个操作数,大多数运算符都是二元运算符。
● 三元运算符:处理三个操作数,只有一个三元运算符。
从功能上划分,运算符主要分为算术运算符、赋值运算符、关系运算符、条件运算符、位运算符和逻辑运算符。
1.算术运算符
算术运算符主要用于数学计算,主要有加法运算符(+),减法运算符(–),乘法运算符(*),除法运算符(/),求模运算符(%),自加运算符(++)和自减运算符(--),如表3-5所示。
表3-5 算术运算符
(续表)
加法运算符、减法运算符、乘法运算符、除法运算符以及模运算符又称为“基本的算术运算符”,它们都是二元运算符,而自增运算符和自减运算符则是一元运算符。算术运算符通常用于整数类型和浮点类型的计算。
对于除法运算符来说,整数相除的结果也应该为整数,如9/5或9/6的结果都为1,而不是1.8及1.5,计算结果要舍弃小数部分。如果除法运算符两边的数据有一个是负数,那么得到的结果在不同的计算机上有可能不同,例如-7/5在一些计算机上结果为-1,而在另一些计算机上结果可能就是-2。通常除法运算符的取值有一个约定俗成的规定,就是按照趋向于0取结果,即-7/5的结果为-1。如果是一个实数与一个整数相除,那么运算结果应该为实数。
a++和++a都相当于a=a+1,其不同之处在于:a++是先使用a的值,再进行a+1的运算;++a则是先进行a+1的运算,再使用a的值。--a和a--类似于++a和a++。初学者一定要仔细注意其中的区别。
当一个或两个操作数为string类型时,二元“+”运算符进行字符串连接运算。如果字符串连接的一个操作数为null,则用一个空字符串代替。另外,通过调用从基类型object继承来的虚方法ToString(),任何非字符串参数将被转换成字符串的表示法。如果ToString()返回null,则用一个空字符串代替。字符串连接运算符的结果是一个字符串,由左操作数的字符后面连接右操作数的字符组成。字符串连接运算符不返回null值。如果没有足够的内存分配给结果字符串,将可能产生OutOfMemoryException异常。
2.赋值运算符
赋值运算符用于将一个数据赋予一个变量、属性或者引用,数据可以是常量,也可以是表达式。前面已经多次使用了简单的等号“=”赋值运算符,例如int num=300,或者int num=a+b。其实,除了等号运算符,还有一些其他的赋值运算符,它们都非常有用。这些赋值运算符都是在“=”之前加上其他运算符,这样就构成了复合的赋值运算符。复合赋值运算符的运算非常简单,例如“c+=18”就等价于“c=c+18”,它相当于对变量进行一次自加操作。表3-6列出了复合赋值运算符的定义和含义。
表3-6 复合赋值运算符
(续表)
3.关系运算符
关系运算符表示了对操作数的比较运算,有关系运算符组成的表达式就是关系表达式。关系表达式的结果只可能有两种,即true或false。常用的关系运算符有6种,如表3-7所示。
表3-7 关系运算符
4.逻辑运算符
逻辑运算符主要用于逻辑判断,主要包括逻辑与,逻辑或和逻辑非。其中,逻辑与和逻辑或属于二元运算符,它要求运算符两边有两个操作数,这两个操作数的值必须为逻辑值。“逻辑非”运算符是一元运算符,它只要求有一个操作数,操作数的值也必须为逻辑值。由逻辑运算符组成的表达式是逻辑表达式,其值只可能有两种,即true或false。表3-8是关于逻辑运算符的说明。
表3-8 逻辑运算符
(续表)
以下通过一段程序来说明如何使用逻辑运算符,代码如下:
上面的代码中,第3行定义的bool类型变量c的值为true,因为a>1的值为true并且b>1的值也为true。第4行的bool变量d的值为false,因为a>9的值为false,只要有一个值为false,最后的结果也就是false。第5行的bool变量e的值为false,因为a<的值为false并且b<1的值也为false。第6行的bool变量f的值为ture,因为a<=9的值为true,只要有一个值为true,最后的结果也就是true。第7行的bool变量g的值为false,因为90>1为true,对它取反后得到的值为false。
5.条件运算符
C#中唯一的一个三元操作符就是条件运算符(?:),由条件运算符组成的表达式就是条件表达式,条件表达式的一般格式为:操作数1?操作数2:操作数3。
其中,“操作数1”的值必须为逻辑值,否则将出现编译错误。进行条件运算时,首先判断问号前面的“操作数1”的逻辑值是真还是假,如果逻辑值为真,则条件运算表达式的值等于“操作数2”的执行结果值;如果为假,则条件运算表达式的值等于“操作数3”的执行结果值。例如下面的代码:
上面的代码中第1行和第2行分别定义了二个整形变量。第3行最后变量c的值为-9,因为因为a>b的值为false。
条件表达式的类型由“操作数2”和“操作数3”控制,如果“操作数2”和“操作数3”是同一类型,那么这一类型就是条件表达式的类型。否则,如果存在“操作数2”到“操作数3”(而不是“操作数3”到“操作数2”)的隐式转换,那么“操作数3”的类型就是条件表达式的类型。反之,“操作数2”的类型就是条件表达式的类型。
6.位运算符
位运算符是以二进制的方式操作数据,并且操作数和结果都是整数类型的数据。位运算符主要包括按位与、按位或、按位异或、按位取反、左移和右移操作。在这些运算符中,除按位取反运算符是一元运算符外,其他的都是二元运算符。位运算符的详细信息和使用方法如表3-9所示。
表3-9 位运算符
7.运算符的优先级
在C#中为上面所述的多种运算符定义了不同的优先级,相同优先级的运算符,除了赋值运算符按照从右至左的顺序执行之外,其余运算符按照从左至右的顺序执行。括号是优先级最高的,可以任意地改变符号的计算顺序。在C#中运算符的优先级定义如表3-10所示,其中1级表示最高优先级,12级表示最低优先级。
表3-10 运算符的优先级
(续表)
【实例3-3】运算符的使用
本例通过控制台接受用户输入的圆半径,然后编程求出圆的面积并将结果显示在控制台屏幕上,具体实现过程如下:
01 启动Visual Studio 2012,执行“文件”︱“新建项目”命令,在弹出的“新建项目”对话框中创建名为“实例3-3”的控制台应用程序。
02 在解决方案资源管理器中生成例“实例3-3”的项目,单击目录下的Program.cs文件,在该文件中编写如下逻辑代码:
代码说明:第1行定义了一个Main函数。第2行定义了一个浮点型双精度类型的常量yuanzoulu表示圆周率。第3行获取用户输入的半径并保存,第5行定义了隐形局部变量area计算圆面积。第6行向控制台屏幕打印输出结果。
03 单击快捷键Ctrl+F5,运行程序的效果如图3-10所示。
图3-10 运行效果
3.2.4 装箱和拆箱
装箱和拆箱是C#的类型关系系统中两个重要的概念,它在值类型和引用类型之间架起了一座桥梁。通过装箱和拆箱操作,可以将任何值类型的变量的值转换为引用类型的变量的值,反之,也可以进行转换。特别的是有了装箱和拆箱操作,就可以使C#类型系统中的任何类型的值最终都可以按对象来处理。
装箱是指将值类型转换为引用类型的过程。对于值类型来说,装箱的具体操作为:首先分配一个对象实例,然后将值类型的值复制到该实例中,装箱前后不是同一个实例。对于引用类型来说,装箱前后都是同一个实例。
拆箱是指将引用类型转换为值类型的过程。拆箱之前,要先检查该对象实例是否为给定值类型的一个装过箱的值,然后将值从实例中赋值出来。
下面的代码分别演示了装箱和拆箱操作:
上面的代码中第1行定义了一个整型变量,第2行把该变量打包到Object引用类型的一个实例中,也就是进行装箱操作。第4行进行拆箱操作。
相对于简单的赋值而言,装箱和取消装箱过程需要进行大量的计算。对值类型进行装箱时,必须分配并构造一个全新的对象。另外拆箱所需的强制转换也需要进行大量的计算。因此,读者在进行装箱和拆箱操作时应该考虑到这两种操作对性能的影响。
3.2.5 控制语句
基本数据类型和控制语句是任何编程语言都必须要有的内容。C#中除了提供顺序结构之外,还提供条件语句和循环语句。
1.条件语句
C#中的条件语句主要有if和switch两类,三元运算符(?:)也有分支的功能,它们都是先对条件进行判断,然后根据判断结果执行不同的分支。三元运算符前面已经介绍过,下面主要介绍if语句和switch语句。
(1)if语句
if语句是最常用的分支语句,使用该语句可以有条件地执行其他语句。if语句的最基本使用格式为:
程序执行时首先检测布尔表达式的值,如果布尔表达式的值是true,就执行if语句中的代码,代码执行完毕后,将继续执行if语句下面的代码。如果布尔表达式的值是false,则直接跳转到if语句后面的代码执行。如果if语句中为代码块(即有多于1行代码),则需要使用大括号“{}”把代码包括起来。当只有一行代码时可以省略大括号。
if语句可以和else关键字合并执行,使用格式如下:
如果布尔表达式的值为真,首先执行语句1的内容,然后再执行if语句后的代码。但是当布尔表达式的值为假时,将首先执行语句2,然后再执行if语句后的代码。同样的如果if语句中为代码块(即有多于1行代码),则需要使用大括号“{}”把代码包括起来。
如果有多个条件需要判断,也可以通过添加else if语句。if语句允许使用嵌套来实现复杂的选择流程。
【实例3-4】if语句的使用
本例在控制台屏幕提供三种不同的输入法供用户选择,接受用户输入的数字,通过if语句判断用户的选择,具体实现步骤如下:
01 启动Visual Studio 2012,执行“文件”︱“新建项目”命令,在弹出的“新建项目”对话框中创建一个名为“实例3-4”的控制台应用程序。
02 在解决方案资源管理器中生成“实例3-4”的项目,单击目录下的Program.cs文件,在该文件中编写如下逻辑代码:
上面的代码中第1行定义了一个Main函数。第6行获得用户输入的数字。第8到第16行使用if…else if语句判断用户输入的数字,并输入对应的选择。第17行到第19行使用eles语句输出输入错误的提示。
03 单击快捷键Ctrl+F5,运行程序的效果如图3-11所示。
图3-11 运行效果
(2)switch语句
switch语句非常类似于if语句,它也是根据测试的值来有条件地执行代码,实际上switch语句完全可以使用if语句代替。一般情况下,如果只有简单的几个分支就需要使用if语句,否则建议使用switch语句。与if语句不同的是,switch语句可以一次将测试变量与多个值进行比较,而不是仅仅测试一个条件,这样可以使代码的执行效率比较高。switch语句的基本语法定义如下:
在switch语句的开始首先计算控制表达式的值,如果该值符合某个case语句中定义的“测试值”就跳转到该case语句执行,当控制表达式的值没有任何匹配的“测试值”时就执行default块中的代码。执行完代码块后退出switch语句,继续执行下面的代码。其中,测试值只能是整数类型或者是字符类型并且各个测试值要互不相同。default语句是可选成分,没有default语句时,如果控制表达式的值没有任何匹配的“测试值”,程序将会退出switch语句转而执行后面的代码。
【实例3-5】switch语句的使用
本例使用switch…case语句完成和实例3-4相同的功能,具体实现步骤如下:
01 启动Visual Studio 2012,执行“文件”︱“新建项目”命令,在弹出的“新建项目”对话框中创建一个名为“实例3-4”的控制台应用程序。
02 在解决方案资源管理器中生成“实例3-5”的项目,单击目录下的Program.cs文件,在该文件中编写如下逻辑代码:
上面代码中第1行定义一个Main函数。第8行~第17行使用switch…case语句判断用户输入的数字,在控制台显示相应的选择结果。其中,第15判断如果输入有误,输出提示信息。
03 单击快捷键Ctrl+F5,程序运行的效果如上图所示。
2.循环语句
循环就是指重复执行一段代码,循环控制可以有条件地实现语句段的循环运行和循环终止。当需要反复执行某些相似的语句时,就可以使用循环语句了,这对于大量的重复操作(上千次,甚至百万次)时尤为重要。C#中的循环语句有四种:do-while循环,while循环,for循环和foreach循环。
(1)do…while语句
do…while语句根据其布尔表达式的值有条件地执行它的循环语句一次或者多次,其语法定义如下:
do…while循环以下述方式执行:程序会首先执行一次循环代码,然后判断布尔表达式的值,如果值为true就从do语句位置开始重新执行循环代码,一直到布尔表达式的值false。所以,无论布尔表达式的值是true还是false,循环代码会至少执行一次。当循环代码要执行多条语句时,要用大括号“{}”把所要执行的语句括起来。
(2)while语句
while循环非常类似于do…while循环,其语法定义如下:
while语句和do…while语句有一个重要的区别:While循环中的布尔测试是在循环开始时进行的,而do…while循环是在最后检测的。如果测试布尔表达式的结果为false,就不会执行循环代码,程序直接跳转到while循环后面的代码执行,而do…while语句则会至少执行一次循环代码。当循环代码要执行多条语句时,要用大括号“{}”把所要执行的语句括起来。
【实例3-6】do…while循环的使用
本例要求用户输入一个正整数,然后程序会计算这个数的阶乘,并输出;接着用户可以再输入另一个正整数计算它的阶乘,直到输入一个负数为止,实现步骤如下:
01 启动Visual Studio 2012,执行“文件”︱“新建项目”命令,在弹出的“新建项目”对话框中创建一个名为“实例3-6”的控制台应用程序。
02 在解决方案资源管理器中生成“实例3-6”的项目,单击目录下的Program.cs文件,在该文件中编写如下逻辑代码:
上面的代码中第1行定义一个Main函数。第3行~第15行使用do…while语句实现循环输入。其中第7行获得用户的输入,第8行~第14行判断用户输入的如果是一个正整数,则进行阶乘的计算并输出计算结果,第15行判断如果用户输入的是负数,则跳出do…while循环。
03 单击快捷键Ctrl+F5,程序运行后的结果如图3-12所示。
图3-12 运行结果
(3)for语句
for循环是最常用的一种循环语句,这类循环可以执行指定的次数,并维护它自己的计数器。for语句首先计算一系列初始表达式的值,接下来当条件成立时,执行其循环语句,之后计算重复表达式的值并根据其值决定下一步的操作。for循环的语法定义如下:
循环变量初始化可以存在也可以不存在,如果该部分存在,则可能为一个局部变量声明和初始化的语句(循环计数变量)或者一系列用逗号分割的表达式。此局部变量的有效区间从它被声明开始到循环语句结束为止。有效区间包括for语句执行条件部分和for语句重复条件部分。
循环条件部分可以存在也可以不存在,如果没有循环停止条件则循环可能成为死循环(除非for循环语句中有其他的跳出语句)。循环条件部分用于检测循环的执行条件,如果符合条件就执行循环代码,否则就执行for循环后面的代码。
循环操作部分也是可以存在或者不存在的,在每一个循环结束或执行循环操作部分,因此通常会在这个部分修改循环计数器的值,使之最终逼近循环结束的条件。当然这并不是必须的,也完全可以在循环代码中修改循环计数器的值。
下面的代码是通过for循环在标准输出设备上打印输出从1~100的数字。
上面的代码的第1行中,程序首先执行int i=1,声明并初始化了循环计数器。然后执行i <= 10,判断i的值是否小于等于10。这里i的值为1,满足循环条件,因此会执行循环代码并在标准输出设备上打印输出1。最后执行i++语句,使得循环计数器的值变为2。
第一个循环完毕后开始执行第二个循环,首先检测i的值是否符合循环条件,如果满足就继续执行循环代码,并在最后更新i的值。如此循环一直到i的值变为101后,循环条件不再满足了,此时跳转到for循环的下一条语句执行。
【实例3-7】for循环的使用
本例在控制台接受用户输入的数字,然后将数字使用冒泡排序的方法进行排列,并将排序后的结果输出到控制台显示,实现的具体步骤如下:
01 启动Visual Studio 2012,执行“文件”︱“新建项目”命令,在弹出的“新建项目”对话框中创建一个名为“实例3-7”的控制台应用程序。
02 在解决方案资源管理器中生成“实例3-7”的项目,单击目录下的Program.cs文件,在该文件中编写如下逻辑代码:
上面的代码中第1行定义了一个Program类。第2行定义了一个静态方法sort,返回一个int类型的数组。第3行到第9行通过一个嵌套的for循环对传递的数组中的元素进行冒泡排序。第12行定义一个Main函数。第14行到第18行通过for循环接受用户输入的数字。第19行调用sort方法获得排序后的数组。第21行到第23行通过一个for循环输出排序后的数字。
03 单击快捷键Ctrl+F5,运行程序后的结果如图3-13所示。
图3-13 运行结果
(4)foreach语句
foreach语句列举出一个集合中的所有元素,并执行关于集合中每个元素的嵌套语句。foreach语句的语法定义如下:
foreach语句括号中的类型和标识符用来声明该语句的循环变量,标识符即循环变量的名称。循环变量相当于一个只读的局部变量,它的有效区间为整个循环语句。在foreach语句执行过程中,重复变量代表着当前操作针对的集合中相关元素。
并非所有的类型都可以用foreach来遍历,可以遍历的类型必须包含公有非静态方法GetEnumerator(),并且由GetEnumerator()返回的结构、类、接口等必须包含一个MoveNext()的方法,返回值为布尔型。
【实例3-8】foreach循环的使用
本例接受用户输入学生的个数和要录入学生的姓名,然后在控制台屏幕显示这些学生的姓名,具体实现步骤如下:
01 启动Visual Studio 2012,执行“文件”︱“新建项目”命令,在弹出的“新建项目”对话框中创建一个名为“实例3-8”的控制台应用程序。
02 在解决方案资源管理器中生成“实例3-8”的项目,单击目录下的Program.cs文件,在该文件中编写如下逻辑代码:
上面的代码中第5行声明一个存放姓名的字符串数组,其长度等于提供的学生人数。第6行到第9行用一个for循环来接受姓名。第11行到第13行使用foreach循环打印姓名到控制台屏幕显示。
03 单击快捷键Ctrl+F5,程序运行的效果如图3-14所示。
图3-14 运行效果
3.2.6 跳转语句
跳转语句可以执行程序的分支,这里语句可以立即传递程序控制,更改流程,进行无条件跳转,在C#中共提供了5种跳转语句。
● break语句:终止并跳出循环。
● continue语句:终止当前的循环,重新开始一个新的循环。
● goto语句:跳转到指定的位置。
● return语句:跳出循环及其包含的函数。
● throw语句:抛出一个异常。
在方法或函数中会使用到return语句,用于退出方法(当然也就退出了循环了),如果需要抛出一个异常则需要使用throw语句。goto语句并不常用,建议读者不要使用goto语句,因为该语句可能会破坏程序的结构性。
break语句用于跳出包含它的switch、while、do、for或者foreach语句。break语句的目标地址为包含它的switch、while、do、for或foreach语句的结尾。假如break不是在switch、while、do、for或者foreach语句的块中,将会发生编译错误。
下面的代码中使用break语句查询1~50中符合条件的数值,如果查找成功就输出数值。然后跳出循环。
上面的代码中第1行~第5行通过for循环遍历1~50。其中,第2行遍历到数值10。第3行使用break语句跳出循环,所以最后输出的数字为1~9。
continue语句用于终止当前的循环,并重新开始新一次包含它的while、do、for或者foreach语句的执行。假如continue语句不被while、do、for或者foreach语句包含,将产生编译错误。以下代码输出50以内的偶数:
上面的代码中第1行到第5行通过for循环遍历1~50。其中,第2行判断如果遍历到数值是奇数。第3行使用continue语句终止当前的循环,重新开始一个新的循环。所以最后输出的数字为50以内的所有的偶数。
3.2.7 异常处理
程序“异常”是指程序运行中的一种“例外”情况,也就是正常情况以外的一种状态。异常对程序可能碰到的错误进行了概括,是错误的集合。如果对异常置之不理,程序会因为它而崩溃。
在程序出现异常的时候,开发人员能够通过有针对性代码的编写来加以处理,在一定的程度上限制异常产生的影响,使程序输出异常信息的同时能得以继续运行。
在一般情况下,会考虑在容易出现异常情况的场合下使用异常处理,例如:
● 算术错误,如以零作除数;
● 方法接收的参数错误;
● 数组大小与实际不符;
● 数字转化格式异常;
● 空指针引用;
● 输入输出错误;
● 找不到文件;
● 不能加载所需的类。
以上例举的仅仅是常用的一些场合。可见在程序中产生异常的情况是非常普遍的。下面将通过代码查看程序中异常的出现:
以上代码运行结果显示:未处理的异常,试图除以零。程序在第3行被迫中止了,以后的代码都未执行。这肯定不是期望的结果。那么,如何通过编码来解决这一情况呢?
在C#中,可以用异常和异常处理程序很容易地将实现程序主逻辑的代码与错误处理代码区分开,所有的异常都是从System.Exception继承而来。此类是所有异常的基类。当发生错误时,系统或当前正在执行的应用程序通过引发包含关于该错误的信息的异常来报告错误。异常发生后,将由该程序或默认异常处理程序处理。
当在一个函数或方法中遇到异常处理的时候,就会创建一个异常处理的对象并在函数中被抛出(throw)。当然,也可以在此函数中处理该异常。为了在函数中实现监视和处理异常的代码,C#提供了三个关键字try、catch和finally。try关键字后面的代码块称为try块,那么catch后的称为catch块,finally形成finally块。标准的异常处理语法定义:
以上代码处理异常的过程如下:
(1)代码运行时,它会尝试执行try块中所有的语句。如果没有任何语句产生一个异常,那么所有语句都会运行。这些语句将一个接一个运行,直到全部完成。然而,一旦出现异常,就会跳出try块,进入一个catch块处理程序中执行。
(2)在try块之后紧接着写一个或多个catch处理程序,用它们处理可能发生的错误。在try块中抛出的所有的异常对象与下面的每个catch块进行比较,判断其中的catch块是否可以捕捉此异常。
(3)如果没有找到匹配的catch块,catch块就不会被执行。非捕获的异常对象由CLR的缺省异常处理器处理,在此情况下,程序会突然终止。
(4)finally块中的代码总是被执行。
要注意的是一个try块可以:
● 有一个或多个相关的catch块,没有finally块。
● 有一个finally块,没有catch块。
● 包含一个或多个catch块,同时有finally块。
【实例3-9】在程序中使用异常处理
本例通过使用try、catch、finally语句块对程序中找不到数据库文件的异常进行处理,具体实现过程如下:
01 启动Visual Studio 2012,执行“文件”︱“新建网站”命令,在弹出的“新建网站”对话框中创建一个名为“实例3-9”的ASP.NET空网站。
02 用鼠标右键单击网站名称“实例3-9”,在弹出的快捷菜单中选择“添加”︱“添加新项”命令,在弹出的“添加新项”对话框中,添加一个名为Default的Web窗体。
03 单击解决方案资源管理器中目录下的Default.aspx.cs文件,在该文件中编写如下逻辑代码:
上面的代码中第1行定义了处理页面加载事件的方法。第2行定义了一个数据库连接字符串。第3行创建数据库连接。第4行打开数据库。
04 单击快捷键Ctrl+F5,程序运行的效果如图3-15所示。因为找不到指定的数据库,而且没有使用异常处理,所以程序中断运行,并给出了系统报错信息。
图3-15 运行结果
05 如果在程序中加入如下的代码使用异常处理:
上面的代码中第4到第14行使用了try、catch、finally异常处理,在catch和finally块中显示自定义错误的提示。
06 单击快捷键Ctrl+F5,程序运行的效果如图3-16所示。当try块执行出错时,执行catch块,所以,浏览器显示系统错误信息和开发人员定义的出错信息,程序得以执行到了最后。
图3-16 运行结果
3.2.8 泛型
通常情况下,泛型在集合中运用的比较多。在System.Collections.Generic名称空间中,包含了一些基于泛型的容器类,例如System.Collections.Generic.Stack、System. Collections. Generic.Dictionary、System.Collections.Generic.List和System.Collections. Generic.Queue等,这些类库可以在集合中实现泛型。泛型的创建格式如下所示:
上面的代码中第1行是泛型创建的格式,使用泛型类必须指定实际的类型,并在“<>”尖括号中指定实际的类型,这里以T进行表示不同的类型。第2行根据格式创建了String类型的泛型类的集合类型list,这意味着list,只能存储String类型的数据。
要使用创建好的泛型应该如下面的代码:
上面的代码中第1行使用泛型Stack <String> list=new Stack <String>创建Stack的对象list,然后第2行和第3行使用Stack类的方法push传入String类型的参数,这里只能传泛型中定义的类型,否则报错。所以第4行使用Stack类的方法connt取得元素个数。
C#泛型类在编译时,先生成中间代码IL,通用类型T只是一个占位符。在实例化类时,根据用户指定的数据类型代替T并由即时编译器(JIT)生成本地代码,这个本地代码中已经使用了实际的数据类型,等同于用实际类型写的类。把为所有类型参数提供参数的泛型类型称为封闭构造泛型类型,简称“封闭类”。不同的封闭类的本地代码是不一样的。按照这个原理,可以这样认为:泛型类的不同封闭类是不同的数据类型。
除了使用系统的泛型类之外,也可以编写自定义的泛型类。下面来介绍泛型类的静态构造函数。泛型中的静态构造函数的原理和非泛型类是一样的,只需把泛型中不同的封闭类理解为不同的类即可。以下两种情况可激发静态的构造函数:
● 特定的封闭类第一次被实例化。
● 特定封闭类中任一静态成员变量被调用。
泛型的静态构造函数只能有一个,而且不能有参数,它只能在被.NET运行时自动调用,而不能人工调用。
由于泛型的出现,导致静态成员变量的机制出现了一些变化:静态成员变量在相同封闭类间共享,不同的封闭类间不共享。这也非常容易理解,因为不同的封闭类虽然有相同的类名称,但由于分别传入了不同的数据类型,他们是完全不同的类,例如下面的代码:
上面的代码中类实例one和two是同一类型,它们之间共享静态成员变量,但类实例three却和one、two是完全不同的类型,所以不能和它们共享静态成员变量。
在编写泛型类时,<T>一般来说不能适应所有类型,但怎样限制调用者传入的数据类型呢?这就需要对传入的数据类型进行约束,约束的方式是指定<T>的祖先,即继承的接口或类。因为C#的单根继承性,所以约束可以有多个接口,但最多只能有一个类,并且类必须在接口之前。
由于通用类型<T>是从object继承来的,所以它在类Node的编写中只能调用object类的方法,这给程序的编写造成了困难。比如类设计只需要支持两种数据类型int和string,并且在类中需要对<T>类型的变量比较大小,但这些却无法实现,因为object是没有比较大小的方法的。为了解决这个问题,只需对<T>进行IComparable约束,这时在类Node里就可以对<T>的实例执行CompareTo方法了。这个问题可以扩展到其他用户自定义的数据类型。
如果在类Node里需要对<T>重新进行实例化该怎么办呢?因为类Node中不知道类<T>到底有哪些构造函数。为了解决这个问题,需要用到new约束,需要注意的是,new约束只能是无参数的,所以也要求相应的类Stack必须有一个无参构造函数,否则编译失败。
上面了解了泛型的基本概念和创建泛型的方法,下面通过具体的例子来演示如何使用泛型。
【实例3-10】泛型的使用
本例接受用户输入的中文类型日期(如:二零一二年二月二十一日),通过控制台应用程序将该日期转换为阿拉伯数字类型(如:2012-2-21)并在屏幕显示,具体实现步骤如下:
01 启动Visual Studio 2012,执行“文件”︱“新建项目”命令,在弹出的“新建项目”对话框中创建一个名为“实例3-10”的控制台应用程序。
02 在解决方案资源管理器中生成“实例3-10”的项目,单击目录下的Program.cs文件,在该文件中编写如下逻辑代码:
上面的代码中第7行定义了一个Dictionary泛型类years,它的参数是两个字符类型。第8行~第10行通过for循环,调用years的Add方法将输入的中文类型的日期添加到泛型中。第11行~第27行利用for循环判断:如果“十”左右字符都在泛型years中,那么“十”消失;如果左边不在右边在,则变1;如果左边在右边不在,则变0;如果左右都不在,则变10。第28行输出转换后的阿拉伯数字类型的日期。
03 单击快捷键Ctrl+F5,程序运行的效果如图3-17所示。
图3-17 运行结果
共有条评论 网友评论