第2章 C#语言编程基础
用C#语言可以创建各种类型的应用程序,其中形式最简单的是控制台应用程序,它可以在命令行执行所有的输入和输出。本章介绍C#语言编程的基础知识,主要内容包括基本语法、数据类型、变量和常量、运算符,以及控制台输入/输出等。
2.1 基本语法
C#语言使用的是Unicode字符集。在编译C#源程序代码的过程中代码中包含的字符被划分为一系列标记,C#编译器能够从中识别出标识符、关键字、常量、运算符和分隔符这5种类型的标记。空格、制表符、回车符及注释都不属于标识符,但可以用于分隔标记。正确地使用标识符和关键字等语言要素,并遵循语法规则来编写程序语句是C#语言编程的基本要求。本节介绍C#语言中的标识符、关键字、语法规则,以及控制台应用程序的基本结构。
2.1.1 标识符
在C#语言中经常要使用标识符为变量、用户定义类型(如类和结构)及其成员指定名称。命名标识符时应当遵循以下规则。
(1)只能包含字母、数字和下画线,而且必须以字母、下画线或字符@开头。
(2)区分大小写,例如,username、UserName和userName分别表示不同的名称。
(3)不能包含标点符号(如句点及问号)、运算符和空格等。
(4)不能使用C#中的关键字,不过关键字添加@符号后可成为合法的标识符。
(5)不能与C#的类库名称相同。
除了上述几条规则以外,命名标识符时还需要参考以下建议。
(1)用Pascal规则来命名类型和方法,如BookStore和ShowBook。
(2)用Camel规则来命名局部变量和方法的参数,如userName和password。
(3)所有的成员变量名称添加前缀m_,如m_connectionString。
(4)接口名称添加前缀I,如ICompare。
(5)自定义属性以Attribute结尾,如AuthorAttribute。
(6)自定义异常以Exception结尾,如ApplicationException。
(7)命名方法时一般采用动宾结构的短语,如AddUser和CreateFile。
2.1.2 关键字
关键字是对编译器具有特殊意义的预定义保留标识符,在程序中不能使用关键字作为标识符。如果一定要使用某个关键字作为标识符,则必须在其前面添加一个@前缀。例如,switch是C#中的一个关键字,不是有效的标识符,但@switch是有效的标识符。
下面列出C#中的所有关键字,它们在C#程序中的任何部分都是保留标识符。
除了上述关键字以外,在C#中还有另一类关键字,称为“上下文关键字”。它仅在受限制的程序上下文中具有特殊含义,并且可以在该上下文外部用做标识符。上下文关键字虽然不是C#中的保留字,但可以用于提供代码中的特定含义。某些上下文关键字(如partial和where)在两个或更多个上下文中具有特殊含义。通常在将新关键字添加到C#语言的同时,也会将其添加为上下文关键字,以便避免破坏用该语言的早期版本编写的程序。
上下文关键字如下。
2.1.3 基本语法规则
编写C#源程序代码时,应遵循以下语法规则。
(1)C#源程序代码由一系列语句组成,每个语句都用一个分号来作为结束符。
(2)在一行上可以放置多个语句,也可以把一个语句拆分到多行上。
(3)代码块使用花括号“{”和“}”作为定界符,一个代码块可以包含零个或多个语句,在“}”后面不需要使用分号。
例如,下面的代码块包含3个代码行。其中包含两个语句,第1个语句位于第一行上;第2个语句被拆分在第2行和第3行上,在第2行末尾没有使用分号:
{ <代码行1,语句1>; <代码行2,语句2> <代码行2,语句2>; }
(4)代码块可以相互嵌套,即在一个代码块中可以包含另一个代码块。
在编写C#代码时,还经常要添加注释。严格地说,注释并不是C#代码,C#编译器会忽略这些内容。但通过注释可以为代码添加描述性文本,使程序更容易阅读。
在C#源程序中添加注释有以下3种方式。
(1)在注释开头添加“/*”标记,在注释末尾添加“*/”标记,语法如下:
/* 注释文本 */
使用这种注释方式时,注释的开始标记和结束标记可以在同一行或不同行上。
(2)在注释开头添加“//”标记,语法如下:
// 注释文本
使用这种注释方式时,注释内容必须放在同一行上。
(3)在注释开头添加“///”标记,语法如下:
/// 注释文本
使用这种注释方式时,注释内容必须放在同一行上。通过配置IDE,可以在编译项目时提取这种形式的注释并创建一个文本文件,该文件可以用于创建文档说明。
代码编辑器提供了快速注释代码的功能,也可以移除注释。
若要注释代码行,可执行以下操作。
(1)在代码编辑器中选择希望将其转换为注释的代码块。
(2)执行以下操作之一。
● 单击“编辑”→“高级”→“注释选定部分”命令。
● 单击“注释选定行”按钮。
代码编辑器在突出显示的代码块中的每行之前添加“//”,将其转换为注释。
2.1.4 控制台应用程序基本结构
控制台应用程序是最简单形式的C#程序,它在命令行执行其所有的输入和输出,对于学习和掌握C#语言功能是一种理想的选择。
在Visual C#中创建一个控制台应用程序项目时,总会自动生成一个名为“Program.cs”的源程序文件并在代码编辑器打开它,其源代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { } } }
下面说明C#控制台应用程序的源代码。
1. 程序结构
在C#中,组织结构的关键概念是程序、命名空间、类型、成员和程序集。C#程序由一个或多个源文件组成,并且程序中声明类型,类型包含成员可以按命名空间组织。类和接口都是类型的示例,字段、方法、属性和事件则是成员的示例。在编译C#程序时,它们被物理地打包为程序集。程序集通常具有文件扩展名.exe或.dll,取决于它们是实现应用程序,还是实现类库。
具有入口点的程序集称为“应用程序”,应用程序运行时将创建新的应用程序域。同一台计算机上可能会同时运行同一个应用程序的多个实例,此时每一个实例都拥有各自的应用程序域。
2. 使用using指令
大多数C#应用程序都是从一个using指令节开始的,该节列出应用程序将会频繁使用的命名空间,以避免开发人员在每次使用其中包含的方法时都要指定完全限定的名称。创建控制台应用程序时在源文件Program.cs的开头包含4个using指令,分别用于导入System、System.Collections.Generic、System.Linq和System.Text。.NET Framework提供了众多的类,而这些类是通过命名空间来组织的。
例如,在控制台应用程序中通过完全限定的名称来调用System.Console.WriteLine方法,可以将指定的数据(后跟当前行终止符)写入标准输出流。一旦通过using指令导入了System命名空间,就可以用Console.WriteLine来代替System.Console.WriteLine,从而简化了代码编写。
3. 声明命名空间
可以使用namespace关键字来声明自己的命名空间,以帮助控制类名称和方法名称的范围,允许开发人员组织代码并提供创建全局唯一类型的方法。使用该关键字声明命名空间时,必须通过一个标识符为此命名空间指定唯一的名称。
一个C#源文件可以包含零个或多个命名空间,无论是否在C#源文件中显式声明了命名空间,编译器都会添加一个默认的命名空间。此命名空间有时称为“全局命名空间”,它存在于每一个文件中,其中的任何标识符都可以用于命名的命名空间中。
当在Visual C#中创建控制台应用程序时,总会以项目名称来作为命名空间的名称,但是命名空间的名称中不能包含运算符。如果创建的项目名称为“ConsoleApplication2-01”(包含减号),则命名空间将被自动改写为“ConsoleApplication2_01”(减号改为下画线)。
4. 声明类
类是C#语言中的一种数据类型,定义了数据类型的数据和行为。类可以包含属性、方法和事件等成员,通过对类进行实例化可以创建对象。在C#中使用class关键字来声明类:
class ClassName { // 声明类的属性、方法和事件等成员 }
当在Visual C#中创建控制台应用程序时,总会在源文件Program.cs中定义一个Program类,该类位于当前项目的命名空间中。
5. 声明Main方法
C#程序必须包含一个Main方法,该方法是应用程序的入口点,用于控制程序的开始和结束。可以在该方法中创建对象和调用其他方法,一个C#程序中只能有一个入口点。
Main方法可以具有下列签名之一:
static void Main() {...} static void Main(string[] args) {...} static int Main() {...} static int Main(string[] args) {...}
由上述签名可知,入口点可以选择返回一个int值,此返回值用于终止应用程序。
入口点可以包含一个可选的形参,该参数可以具有任意名称。但其类型必须为string[],即字符串数组。若存在形参,执行环境会创建并传递一个包含命令行参数的string[]实参,这些命令行参数是在启动应用程序时指定的。string[]参数永远不能为null,但如果没有指定命令行参数,则其长度可以为零。
当在Visual C#中创建控制台应用程序时,总会在Program类中定义一个Main方法:
static void Main(string[] args) { // 在这里编写要执行的代码 }
其中关键字static是一个修饰符,表示Main方法是静态的。即该方法属于类型本身,而不是属于特定对象的静态成员;关键字void表示Main方法没有返回值;圆括号中的string[] args表示Main方法的参数是一个字符串数组,其中string是System.String类的别名。
使用Main方法时,应注意以下几点。
(1)Main方法在类或结构的内部声明,而且必须使用static关键字将其声明为静态方法,而不应为公共方法(public)。在上例中,Main方法接受默认访问级别private。
(2)Main方法可以具有void或int返回类型。
(3)声明Main方法时可以使用或不使用参数。
(4)参数可以作为从零开始索引的命令行参数来读取。例如,在Main方法中可以通过args[0]来获取第1个命令行参数;通过args[1]来获取第2个命令行参数,依此类推。当在命令行执行程序时,可以使用空格来分隔可执行文件名与参数或不同参数。
(5)与C和C++不同,可执行文件名不会被当做第1个命令行参数。
6. 控制台输入/输出
在C#控制台应用程序中,可以通过调用System.Console类的以下方法来实现输入/输出。
(1)Clear:清除控制台缓冲区和相应的控制台窗口的显示信息。
(2)Read:从标准输入流读取下一个字符。
(3)ReadKey:获取用户按下的下一个字符或功能键。
(4)ReadLine:从标准输入流中读取下一行字符。
(5)Write:将指定值的文本表示形式写入标准输出流。
(6)WriteLine:将指定数据(后跟当前行终止符)写入标准输出流。
【例2-1】创建一个C#控制台应用程序,用于说明如何实现控制台输入/输出及获取命令行参数。当在命令提示符下运行该应用程序时,如果未在可执行文件名后面附加用户名,则提示输入用户名。输入用户名并按Enter键后显示欢迎信息,如图2-1所示;如果在可执行文件名后面附加了用户名,则显示欢迎信息,如图2-2所示。
图2-1 欢迎信息
图2-2 欢迎信息
【设计步骤】
(1)在Visual C#中创建一个控制台应用程序项目,项目名称为“ConsoleApplication2-01”。保存在F:\Visual C sharp 2008\chapter02文件夹中,解决方案名称为“chapter02”。
(2)在代码编辑器中打开源文件Program.cs,然后在Main方法中添加以下代码:
string m UserName; // 在Main方法中声明一个局部变量 if (args.Length > 0) // 如果提供了命令行参数(数组元素数大于0) { m UserName = args[0]; // 获取第1个命令行参数并存储在变量m UserName中 } else // 如果未提供命令行参数 { Console.Write("请输入用户名:"); // 显示提示信息 m UserName = Console.ReadLine(); // 从控制台读取一行字符串并存储到变量m UserName中 } Console.WriteLine("{0}用户,欢迎您进入C#编程世界!", m UserName);
(3)选择“项目”→“生成ConsoleApplication2-01”命令或按Shift+F6组合键以编译源文件。
(4)在Windows资源管理器中导航到F:\Visual C sharp 2008\chapter02\ConsoleApplication2-01\bin \Debug文件夹,找到可执行文件ConsoleApplication2-01.exe并将其复制到C:盘根目录中。
(5)单击“开始”→“运行”命令,显示“运行”对话框。在“打开”下拉列表框中输入cmd,然后单击“确定”按钮。
(6)在命令行中输入不带参数的命令ConsoleApplication2-01,当出现提示信息时输入用户名,然后按Enter键。在命令行中输入包含参数的命令ConsoleApplication2-01 <用户名>,然后按Enter键。
2.2 数据类型
C#是强类型语言,使用每个变量和对象前都必须声明其数据类型。C#的数据类型分为3类,即值类型、引用类型和指针类型,值类型用于存储实际数据本身;引用类型用于存储对实际数据的引用;指针类型仅用于不安全代码。在本书中仅说明值类型和引用类型,对指针类型感兴趣的读者可以查阅相关资料。
2.2.1 值类型
所有的值类型均隐式派生自System.ValueType,每种值类型均有一个隐式的默认构造函数来初始化其类型默认值。值类型变量直接包含值,该值存储在堆栈中,堆栈用于存储具有固定长度的数据。将一个值类型变量赋给另一个值类型变量时,将复制变量包含的值。值类型分为简单类型、结构类型和枚举类型,下面分别加以讨论。
1. 简单类型
所有的简单类型都是C#语言内置的组成部分,都是 .NET Framework系统类型的别名。例如,int是System.Int32的别名。表2-1所示为C#中的所有简单类型及其别名、大小和范围。
表2-1 简单类型
C#类型的关键字及其别名都可以用于声明变量的数据类型,而且二者可以互换。例如,可以使用下列两种声明之一来声明一个整数变量:
int x = 123; System.Int32 x = 123;
若要显示某个C#类型的实际类型,可使用系统方法GetType()。例如,如下语句表示myVariable类型的系统别名:
Console.WriteLine(myVariable.GetType());
简单类型可以分为4类,即布尔类型、整数类型、实数类型和字符类型。
(1)布尔类型:bool关键字用于声明变量,以存储布尔值true和false。在程序中可以将布尔值赋给bool变量,也可以将计算结果为bool类型的表达式赋给bool变量。在C#中,不存在bool类型与其他类型之间的相互转换。布尔型变量可以用做if语句的条件,其他类型则不能。
(2)整数类型:包括sbyte、byte、short、ushort、int、uint、long、ulong和char,这些关键字用于声明整型变量,以存储整数值。使用整数类型时,应注意以下几点。
● 如果整数没有后缀,则其类型为以下类型中可表示其值的第1个类型,即int、uint、long及ulong。
● 当使用后缀L时,将根据整数的大小确定其类型是long,还是ulong;当使用后缀U或u时,将根据文本的数值来确定类型是uint,还是ulong;如果同时使用后缀U和L,则为ulong类型。
● 如果整数表示的值超出了ulong的范围,将产生编译错误。
● 使用0x前缀表示十六进制整数,例如0xfa28。
(3)实数类型:包括float、double和decimal,其中float用于声明变量以存储32位浮点值;double用于声明变量以存储64位浮点值;decimal用于声明十进制变量以存储128位数据,精度可达28~29位有效位。与浮点型相比,decimal类型具有更高的精度和更小的范围,适合于财务和货币计算。使用实数类型时,应注意以下几点。
● 默认情况下,赋值运算符右侧的实数被视为double。如果未使用后缀,则使用实数为float变量或decimal变量赋值时,将导致编译器错误。例如,下面两个语句都是错误的:
float myFloat = 1.23; decimal myDecimal = 4.56;
● 若要指定实数的数据类型,则使用后缀f或F初始化float变量;使用后缀d或D初始化double变量;使用后缀m或M初始化decimal变量。例如:
float myFloat = 1.23F; decimal myDecimal = 4.56M;
● 整型被隐式转换为decimal,其计算结果为decimal。可以用整数初始化decimal变量,而不使用后缀。例如:
decimal myDecimal = 456;
● 在浮点型和decimal类型之间不存在隐式转换,必须使用强制转换在这两种类型之间转换。
(4)字符类型:char关键字用于声明U+0000~U+ffff范围内的Unicode字符。Unicode字符是16位字符,用于表示世界上大多数已知的书面语言。char类型的常数使用单引号作为定界符,可以写成字符、十六进制换码序列(加前缀\x)或Unicode表示形式(加前缀\u),也可以显式转换整数字符代码。以下所有语句均声明了一个char变量并使用字符A对其初始化:
char char1 = 'A'; // 字符 char char2 = '\x0041'; // 十六进制数 char char3 = (char)65; // 显式转换整数 char char4 = '\u0041'; // Unicode形式
还可以使用如表2-2所示的转义字符为字符型变量赋值。
表2-2 转义字符
char可以隐式转换为ushort、int、uint、long、ulong、float、double或decimal,但是不存在从其他类型到char类型的隐式转换,不能使用其他类型数据(如整数)为字符型变量赋值。
【例2-2】创建一个C#控制台应用程序,用于说明如何使用简单类型声明变量并对其初始化,程序运行结果如图2-3所示。
图2-3 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-02”。保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为解决方案的启动项目。
(2)打开源文件Program.cs,在Program类的Main方法中输入以下代码:
bool m Boolean = true; byte m Byte = 228; sbyte m Sbyte = -123; char m Char = 'M'; decimal m Decimal = 123456789M; double m Double = 6789.123456; float m Float = 789.2038F; int m Integer = -12345; uint m UnsignedInteger = 8909908U; long m Long = -9123372036854775801L; ulong m UnsignedLong = 93284784327893498UL; short m Short = -30729; ushort m UnsignedShort = 34567; Console.WriteLine("m Boolean: \t\t值={0},类型={1}", m Boolean, m Boolean.GetType()); Console.WriteLine("m Byte: \t\t值={0},类型={1}", m Byte, m Byte.GetType()); Console.WriteLine("m Sbyte: \t\t值={0},类型={1}", m Sbyte, m Sbyte.GetType()); Console.WriteLine("m Char: \t\t值={0},类型={1}", m Char, m Char.GetType()); Console.WriteLine("m Decimal: \t\t值={0},类型={1}", m Decimal, m Decimal.GetType()); Console.WriteLine("m Double: \t\t值={0},类型={1}", m Double, m Double.GetType()); Console.WriteLine("m Float: \t\t值={0},类型={1}", m Float, m Float.GetType()); Console.WriteLine("m Integer: \t\t值={0},类型={1}", m Integer, m Integer.GetType()); Console.WriteLine("m UnsignedInteger:\t值={0},类型={1}",m UnsignedInteger,m UnsignedInteger.GetType()); Console.WriteLine("m Long: \t\t值={0},类型={1}", m Long, m Long.GetType()); Console.WriteLine("m UnsignedLong: \t值={0},类型={1}",m UnsignedLong,m UnsignedLong.GetType()); Console.WriteLine("m Short: \t\t值={0},类型={1}", m Short, m Short.GetType()); Console.WriteLine("m UnsignedShort: \t值={0},类型={1}\n", m UnsignedShort,m UnsignedShort.GetType());
(3)按Ctrl+F5组合键编译并运行应用程序。
2. 结构类型
结构类型用关键字struct声明,它是一种值类型,通常用来封装小型相关变量组,其中每个变量为结构的成员。例如,下面的语句声明了一个名为“Book”的结构类型。其中包含3个字段成员,分别表示价格、书名和作者:
public struct Book { public decimal price; public string title; public string author; };
需要注意的是,在结构类型定义中声明结构成员时通常应添加public修饰符;否则无法在结构之外访问其成员。
声明一个结构类型后,可以使用该类型来声明变量。若要访问结构变量的成员,则可以在结构变量与成员变量之间使用圆点访问符“.”,即采用“结构变量名.结构成员名”形式。例如,在下面的语句中使用结构类型声明了一个名为“Book”的结构变量,然后为其各个成员赋值:
Book aBook; aBook.price = 29.8m; aBook.title = "JSP动态网站开发"; aBook.author = "赵增敏";
除了字段以外,结构还可以包含构造函数、常量、方法、属性、索引器、运算符、事件和嵌套类型。如果同时需要包含上述几种成员,可考虑改为使用类作为类型。结构可以实现接口,但无法继承另一个结构。
【例2-3】创建一个C#控制台应用程序,用于说明如何创建结构类型并用来声明结构变量,程序运行结果如图2-4所示。
图2-4 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-03”。保存在F:\Visual C sharp 2008\chapter02文件夹中,并将该项目设置为解决方案的启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在类Program的定义上方创建一个名为“Student”的结构类型,代码如下:
struct Student // 声明结构类型 { // 为结构声明3个字段 public string StudentID; public string Name; public byte Age; public void ShowStudentInfo() // 为结构声明一个方法 { Console.WriteLine("学号:{0}", this.StudentID); Console.WriteLine("姓名:{0}", this.Name); Console.WriteLine("年龄:{0}\n", this.Age); } };
(3)在Main方法中添加以下代码:
Student student1; // 声明结构变量 // 对结构的字段成员赋值 student1.StudentID = "001"; student1.Name = "李小明"; student1.Age = 22; Console.WriteLine("学生信息如下:"); student1.ShowStudentInfo(); // 调用结构的方法
(4)按Ctrl+F5组合键编译并运行应用程序。
3. 枚举类型
枚举类型是一种由一组命名常量(称为“枚举数列表”)组成的独特类型,其中每个命名常量称为“枚举的成员”,可以通过“枚举名.成员名”语法格式访问枚举成员。在编译时,对枚举中各个值的所有引用均将转换为数值文本。
枚举类型可以使用enum关键字来声明每种枚举类型都具有的基础类型,该类型可以是除char以外的任何整型。枚举元素的默认类型为int,声明枚举类型时需要把数据一一列举出来。默认情况下,第1个枚举数的值为0,后面每个枚举数的值依次递增1。例如,下面的代码声明了一个名为“Days”的枚举类型:
enum Days {Sat, Sun, Mon, Tue, Wed, Thu, Fri};
在此枚举类型中Sat为0,Sun为1,Mon为2,依此类推。
声明枚举类型时也可以为枚举成员数设置初始值,例如:
enum Days {Sat = 1, Sun,Mon, Tue, Wed, Thu, Fri};
在此枚举中强制枚举元素序列从1,而不是从0开始。
若要声明另一种整型枚举(例如byte),可以在标识符之后紧跟类型并在二者之间使用冒号:
enum Days : byte {Sat = 1, Sun, Mon, Tue, Wed, Thu, Fri};
允许使用的枚举类型有byte、sbyte、short、ushort、int、uint、long及ulong。
使用enum关键字创建一个枚举类型后可以使用该枚举类型来声明变量,还可以使用枚举中的某个成员的值为枚举类型的变量赋值。例如,下面的代码声明一个枚举变量并为其赋值:
Days workDay; workDay = Days.Mon;
2.2.2 引用类型
引用类型的变量又称为“对象”,用于对实际数据的引用,即存储实际数据的地址。引用类型的变量把实际数据的地址存储在堆栈中,而把实际数据存储在堆中,堆一般用于存储可变长度的数据。引用类型变量的赋值只复制对对象的引用,而不复制对象本身。因此如果两个引用变量指向同一个对象,则对其中一个变量的操作将会影响到另一个变量。在C#中有4 种引用类型,即类、数组、接口和委托。特殊值null适用于任何引用类型,表示不引用任何对象,是引用类型变量的默认值。
1. 类
类是C#中功能最强大的数据类型,它定义了数据的类型和行为,并且支持继承。在程序中首先声明一个类,然后就可以使用该类来创建对象作为类的实例。
类使用关键字class声明,例如,下面的语句声明了一个名为“Student”的类:
public class Student { // 在这里定义方法、属性、字段、事件及委托 // 以及嵌套类等成员 }
class关键字前面是访问级别,在上例中使用public表示任何程序都可以基于该类创建对象。类的名称位于class关键字的后面,花括号中的部分是类的主体,用于定义行为和数据。包括类的字段、属性、方法以及事件等,统称为“类的成员”。
类定义了对象的类型,但类并不是对象本身。对象是基于类的具体实体,有时也称为“类的实例”。定义一个类以后,就可以通过使用new关键字后跟类名称来创建对象:
Student student1 = new Student();
创建类的实例后,将传递回对该对象的引用。在上例中student1是对基于Student类的对象的引用,student1引用了新对象,但不包含对象数据本身。也可以在不创建对象的情况下创建对象引用:
Student student2;
上述代码创建了一个对象引用student2,它并不引用任何对象。建议不要创建这样的对象引用,因为在运行时通过这样的引用来访问对象的尝试将会失败。若要通过一个对象引用来引用对象,可以创建新的对象。也可以用其来引用现有的对象,例如:
Student student3 = new Student(); Student student4 = student3;
上述代码创建了两个对象引用,它们指向同一个对象。通过student3对对象所做的任何更改都将反映在随后使用的student4中。由于基于类的对象是按引用来引用的,因此类称为“引用类型”。
类支持继承,继承通过使用派生来实现。而派生意味着类使用基类声明,其数据和行为从基类继承,从而可以实现代码重用。通过在派生的类名后面添加冒号和基类名称可以指定基类,例如:
public class Manager : Employee { // 基类Employee的字段、属性、方法和事件成员被继承 // 可以为派生类Manager定义新的字段、属性、方法和事件…… }
当根据基类声明新类时,为基类定义的所有类成员也将成为新类的一部分。因为基类自身也可能继承自另一个类,而后者又从另一个类继承。依此类推,类可能具有很多个基类。
在C#中经常用到以下两个类。
(1)Object类:位于System命名空间中,是类型层次结构的根,也是 .NET Framework中所有类的最终基类。Object类支持 .NET Framework类层次结构中的所有类,并为派生类提供低级别服务。在C#的统一类型系统中,所有类型(预定义类型、用户定义类型、引用类型和值类型)都是直接或间接从其继承的。而且继承是隐式的,通常并不要求声明类时从Object类继承。在C#中,object是System.Object的别名。通过object可以声明对象变量,并且可以将该变量的初始值设置为任何数据类型。例如:
object obj1; // 声明一个Object类型变量 object obj2 = 123; // 声明Object类型变量并使用整数对其初始化 object obj3 = 'D'; // 声明Object类型变量并使用字符对其初始化 object obj4 = 123.456f; // 声明Object类型变量并使用浮点数对其初始化
(2)String类:位于System命名空间中,表示由一系列Unicode字符组成的文本字符串。在C#中,string是System.String的别名,可以通过string声明字符串变量并使用字符串常量初始化。还可以通过下标从字符串中获取指定字符,例如:
string str1; // 声明一个字符串变量 string str2 = "贯通Visual C# 2008程序设计"; // 声明一个字符串变量并对其进行初始化 string str3 = str2; // str3和str2指向同一个字符串 char ch1 = str3[1]; // 从字符串中取出一个字:“通”
应当注意的是,字符串常量必须使用双引号作为定界符,而不能像字符常量那样使用单引号作为定界符;否则会出现编译错误。
【例2-4】创建一个C#控制台应用程序,说明如何声明类并用来创建对象以计算圆的面积,程序运行结果如图2-5所示。
图2-5 程序运行结果
【设计步骤】
(1)在Visual C#中打开解决方案chapter02,在其中添加一个控制台应用程序项目。项目名称为“ConsoleApplication2-04”,保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为当前解决方案的启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在Main方法之前声明一个名为“Circle”的类:
class Circle // 声明类 { public double Radius; // 为类定义字段 public double GetArea() // 为类定义方法 { return System.Math.PI * this.Radius * this.Radius; // 设置方法的返回值 } }
(3)在Main方法中添加以下代码:
Circle aCircle=new Circle(); // 对类实例化并返回对象引用 aCircle.Radius = 3; // 设置类字段的值 Console.WriteLine("圆半径={0}", aCircle.Radius); // 访问类的字段 Console.WriteLine("圆面积={0}\n", aCircle.GetArea()); // 访问类的方法
(4)保存所有文件,并按Ctrl+F5组合键来运行应用程序。
2. 数组
数组是一种数据结构,包含若干相同类型的变量,每个变量都是一个数组元素。数组类型的值是对象,数组对象定义为存储数组元素类型的值的一系列位置。数组类型是从类型System.Array继承而来,而且可以使用类型来声明数组并使用new运算符初始化数组元素:
dataType[] arrayName = new dataType[size];
其中dataType指定数组元素的数据类型,arrayName指定数组名称,size指定数组中的元素个数。
例如,如下代码声明了一个由5个整数组成的数组:
int[] array = new int[5];
此数组包含5个元素,即array[0]~array[4]。new运算符用于创建数组并将数组元素初始化为其默认值,上例中的所有数组元素都被初始化为零。
也可以在声明数组时将其初始化,在这种情况下不需要提供元素个数,元素个数由初始化列表中的初始值的个数提供。例如:
int[] array1 = new int[] { 1, 3, 5, 7, 9 };
此数组包含5个元素,即aray1[0]=1、array1[1]=3、array1[2]=5、array1[3]=7且array1[4]=9。
【例2-5】创建一个C#控制台应用程序,用于说明如何创建数组并访问数组元素,程序运行结果如图2-6所示。
图2-6 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-05”。保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在Main方法中添加以下代码:
int i; int[] array1 = new int[30]; // 声明一个数组,其中包含30个整数 for (i = 0; i < array1.Length; i++) // 通过循环语句遍历数组,为每个元素赋值 { array1[i] = i+1; // 为数组元素array1[i]赋值 } for (i = 0; i < array1.Length; i++) // 通过循环语句输出数组中每个元素的值 { Console.Write(array1[i]); // 输出数组元素array1[i]的值 Console.Write('\t'); // 输出一个水平制表符 if ((i+1) % 6 == 0) Console.WriteLine(); // 若满足条件,则输出一个换行符 } Console.WriteLine();
(3)按Ctrl+F5组合键编译并运行应用程序。
3. 接口
接口只包含方法、委托或事件的签名,签名由返回类型和参数组成。在C#语言中接口用关键字interface声明,例如:
interface ISampleInterface // 声明接口 { void SampleMethod(); // 方法签名 }
接口方法的实现在实现接口的类中完成,例如:
class ImplementationClass : ISampleInterface // 基于接口声明类 { void ISampleInterface.SampleMethod() // 显式实现接口方法 { // 在这里编写接口方法的实现代码 } }
接口具有以下特点。
(1)接口可以是命名空间或类的成员,并且可以包含方法、属性、索引器,以及事件成员的签名。
(2)一个接口可以从一个或多个基接口继承。
(3)当基类型列表包含基类和接口时,基类必须是列表中的第1项。
(4)实现接口的类可以显式实现该接口的成员。
(5)显式实现的成员不能通过类实例访问,而只能通过接口实例访问。
【例2-6】创建一个C#控制台应用程序,用于说明通过类实现接口并显式实现该接口的成员,程序运行结果如图2-7所示。
图2-7 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-06”,保存在F:\Visual C sharp 2008\chapter02文件夹中,并将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在Program类定义上方声明一个接口:
interface ISampleInterface // 声明接口(命名空间的成员) { void SampleMethod1(); // 方法签名 int SampleMethod2(int x, int y); // 方法签名 }
(3)修改Program类定义,使该类从接口ISampleInterface派生并显式实现接口方法,然后在Main方法中添加如下代码:
class Program: ISampleInterface { void ISampleInterface.SampleMethod1() // 实现接口方法 { Console.WriteLine("通过类实现接口!"); } int ISampleInterface.SampleMethod2(int x, int y) { return x + y; } static void Main(string[] args) { ISampleInterface obj = new Program(); // 声明接口实例 obj.SampleMethod1(); // 调用接口实例的方法SampleMethod1 int x = 12345; int y = 67890; int z = obj.SampleMethod2(x, y); // 调用接口实例的方法SampleMethod2并获取其返回值 Console.WriteLine("x + y = {0}\n", z); } }
(4)按Ctrl+F5组合键编译并运行应用程序。
4. 委托
委托是一种引用方法的类型,从System.Delegate类派生而来。作为一种数据结构,它引用静态方法或类实例及该类的实例方法。一旦为委托分配了方法,则其将与该方法具有完全相同的行为。委托方法的调用可以与其他任何方法一样,具有参数和返回值。
委托用关键字delegate来声明,例如:
public delegate int ArithmeticOperation(int x, int y);
在上述代码中声明了一个名为“ArithmeticOperation”的委托,它带有两个int类型的参数,而且返回值也是int类型。
委托具有以下特点。
(1)类似于C++中的函数指针,但是类型是安全的。
(2)允许将方法作为参数传递。
(3)可以用于定义回调方法。
(4)可以链接在一起,例如,可以对一个事件调用多个方法。
(5)方法不必与委托签名完全匹配。
C# 2.0中引入了匿名方法的概念,此类方法允许将代码块作为参数传递,以代替单独定义的方法;C# 3.0中引入了Lambda表达式,用其可以更简练地编写内联代码块。匿名方法和Lambda表达式(在某些上下文中)都可以编译为委托类型,这些功能统称为“匿名函数”。
与委托签名匹配的任何可访问类或结构中的任何方法都可以分配给该委托,方法可以是静态方法,也可以是实例方法,可以通过编程方式来更改方法调用,还可以在现有类中插入新代码。只要知道委托的签名,就可分配自己的委托方法。
将方法作为参数进行引用的能力使委托成为定义回调方法的理想选择,例如,可以为排序算法传递对比较两个对象的方法的引用。
使用委托主要包括3个步骤,即声明、实例化和调用。
【例2-7】创建一个C#控制台应用程序,用于说明如何声明、实例化和调用委托,程序运行结果如图2-8所示。
图2-8 程序运行结果
【设计步骤】
(1)在Visual C#中打开解决方案chapter02,在其中添加一个控制台应用程序项目。项目名称为“ConsoleApplication2-07”,保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,并在Program类的Main方法定义上方声明一个委托和两个静态方法:
public delegate int ArithmeticOperation(int x, int y); // 声明一个委托 static int Add(int x, int y) // 定义一个静态方法 { return x + y; } static int Subtract(int x, int y) // 定义另一个静态方法 { return x - y; }
(3)在Main方法中添加以下代码:
int a = 9; int b = 6; ArithmeticOperation myDelegate = new ArithmeticOperation(Add); // 声明一个委托并对其实例化 Console.WriteLine("a + b = {0}", myDelegate(a, b)); // 调用委托并获取其值 myDelegate = Subtract; // 对委托分配另一个静态方法 Console.WriteLine("a - b = {0}\n", myDelegate(a, b)); // 调用委托并获取其值
(4)按Ctrl+F5组合键编译并运行应用程序。
2.2.3 数据类型转换
在实际开发中,经常要对不同类型数据执行混合运算或将一种简单类型转换为另一种简单类型,这将会对数据的存储空间和精度产生影响,为此应了解不同数据类型之间的转换。在C#语言中,有两种形式的数据类型转换,即隐式类型转换和显式类型转换。前者是系统默认的,由编译器根据不同类型之间的转换规则自动完成;后者则必须使用强制类型转换符来实现。
1. 隐式类型转换
隐式类型转换可能在多种情形下发生,包括调用方法时和在赋值语句中,包括隐式数值转换、隐式枚举转换和隐式引用转换。
(1)隐式数值转换:如表2-3所示为预定义的隐式数值转换。
表2-3 隐式数值转换
隐式数值转换需要注意以下几个问题。
● 从int、uint或long到float的转换,以及从long到double的转换的精度可能会降低,但数值大小不受影响。
● 不存在到char类型的隐式转换。
● 不存在浮点型与decimal类型之间的隐式转换。
● int类型的常数表达式可以转换为sbyte、byte、short、ushort、uint或ulong,前提是常数表达式的值处于目标类型的范围之内。
(2)隐式枚举转换:在C#中允许把十进制整数0转换为枚举类型,但不允许对其他整数执行这种隐式转换。
(3)隐式引用转换:在引用类型之间的转换,主要包括以下几种情况。
● 从任意引用类型到Object类型。
● 从类类型S到类类型T,其中类S从类T派生。
● 从类类型S到接口类型T,其中类S实现了接口T。
● 从接口类型S到接口类型T,其中接口S从接口T派生。
● 从任意数组类型到System.Array类型。
● 从任意委托类型到System.Delegate类型。
● 从任意数组类型或委托类型到System.ICloneable类型。
● 从null类型到任意引用类型。
2. 显式类型转换
若要执行显式类型转换,可以把目标类型名放在圆括号内,后面跟要转换的变量名或表达式。语法格式如下:
(目标类型名)变量或表达式
例如,在下面的语句中分别声明了一个整型变量i和一个浮点型变量f,然后通过显式类型转换把变量f的值转换为整型数据并赋给变量i。结果只保存了浮点数的整数部分而舍弃了小数部分,因此变量i的值为1,显示结果为1:
int i; float f = 1.23; i = (int)f; Console.WriteLine("i={0}", i);
显式类型转换可以分为3种情况,即显式数据转换、显式枚举转换及显式引用转换。
(1)显式数值转换:用于通过显式转换表达式将一种数字类型转换为另一种数字类型,如表2-4所示为这些转换的源类型和目标类型。
表2-4 显式数值转换
使用显式数值转换时,应注意以下几个问题。
● 可能导致精度损失或引发异常。
● 将decimal值转换为整型时,该值将舍入为与零最接近的整数值。如果结果整数值超出目标类型的范围,则会引发OverflowException。
● 将double或float值转换为整型时,值会被截断。如果该结果整数值超出了目标值的范围,则结果将取决于溢出检查上下文。在checked上下文中,将引发OverflowException;而在unchecked上下文中,结果将是一个未指定目标类型的值。
● 将double转换为float时,double值将舍入为最接近的float值。如果double值因过小或过大而使目标类型无法容纳它,则结果将为零或无穷大。
● 将float或double转换为decimal时,源值将转换为decimal表示形式,并舍入为第28个小数位之后最接近的数(如果需要)。如果源值因过小而无法表示为decimal,那么结果将为零;如果源值为NaN(非数字值)、无穷大或因过大而无法表示为decimal,则会引发OverflowException。
● 将decimal转换为float或double时,decimal值将舍入为最接近的double或float值。
(2)显式枚举转换:在枚举成员的数据类型与相应的目标类型之间进行显式转换或者隐式转换。
例如,如果声明了一个枚举类型E,其成员类型为int,则从E到byte到转换即从int到byte的显式类型转换;从byte到E的转换则被作为一个从byte到int的隐式类型转换来处理。
显式枚举转换有3种情况,即数字类型转换为枚举类型、枚举类型转换为数字类型,以及一种枚举类型转换为另一种枚举类型。
显式枚举转换的3种情况如表2-5所示。
表2-5 显式枚举转换的3种情况
(3)显式引用转换:发生在不同引用类型之间,需要在运行时检测,以保证转换的成功。为了保证转换成功,要求源类型变量的值必须为null,或者其引用的对象可以隐式地转换为目标类型;否则显式引用转换失败并引发一个InvalidCastException异常。
显式引用转换主要包括以下几种情况。
● 从Object类型到任意引用类型。
● 从类类型S到类类型T,其中S是T的基类。
● 从类类型S到接口类型T,其中S没有被封装,并且S没有实现T。
● 从接口类型S到类类型T,其中T没有被封装,并且T没有实现S。
● 从接口类型S到接口类型T,其中S不是从T派生的。
● 从System.Array到任意数组类型。
● 从System.Delegate到任意委托类型。
● 从System.ICloneable到任意数组类型或委托类型。
2.2.4 装箱与拆箱
如前所述,在C#中不同的值类型和不同的引用类型之间可以转换。值类型与引用类型的区别在于值类型变量直接包含值,该值存储在堆栈中;引用类型的变量(对象变量)是把实际数据的地址存储在堆栈中,而把实际数据存储在堆中。在C#中可以通过装箱和拆箱来实现值类型与对象之间的相互转换,从而在值类型与引用类型之间建立联系。
1. 装箱
装箱指值类型到对象的转换,意味着值类型实例在运行时将携带完整的类型信息并在堆中为其分配空间。Microsoft中间语言(MSIL)指令集中的box指令通过复制值类型并将其嵌入到新分配的对象中将值类型转换为对象。
装箱用于在垃圾回收堆中存储值类型,是值类型到object类型或到其所实现的任何接口类型的隐式转换。对值类型装箱将把该值类型打包到object引用类型的一个实例中,即在堆中分配一个对象实例并将该值复制到新的对象中。
例如,在下面的语句中首先声明一个值类型变量i并对始化,然后对该变量执行隐式装箱操作:
int i = 123; object o = i; // 隐式装箱
第2个语句的执行结果是在堆栈中创建对象引用o,而在堆中则引用int类型的值。该值是赋给变量i的值类型值的一个副本,变量i与该副本是相互独立的。如图2-9所示为变量i与变量o之间的差异。
图2-9 变量i与变量o之间的差异
也可以如下例显式执行装箱,不过显式装箱从来不是必需的:
int i = 123; object o = (object)i; // 显式装箱
【例2-8】创建一个C#控制台应用程序,说明如何通过装箱把整型变量转换为对象,程序运行结果如图2-10所示。
图2-10 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-08”。保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,并在Main方法中添加以下代码:
int i = 123; object o = i; // 隐式装箱 System.Console.WriteLine("i = {0},o = {1}", i, o); i = 456; // 更改i的值 System.Console.WriteLine("i = {0},o = {1}", i, o); o = 789; // 更改o的值 System.Console.WriteLine("i = {0},o = {1}\n", i, o);
(3)按Ctrl+F5组合键编译并运行程序。
2. 拆箱
拆箱又称为“取消装箱”,指从object类型到值类型或从接口类型到实现该接口的值类型的显式转换。拆箱操作包括两个步骤,即首先检查对象实例以确保其是给定值类型的装箱值,然后将该值从实例复制到值类型变量中。
下面的语句说明了装箱和拆箱操作:
int i = 123; // 声明值类型变量并初始化 object o = i; // 对int值类型变量i装箱(隐式类型转换),由此创建对象o int j = (int)o; // 对object类型变量o拆箱(显式类型转换),将o的值复制到值类型变量i中
上述语句的执行结果如图2-11所示。
图2-11 装箱与拆箱的执行结果
若要在运行时成功地对一个装箱值类型进行拆箱,被拆箱的项必须是对一个对象的引用,该对象是先前通过对该值类型实例进行装箱创建的。尝试对null值或不兼容值类型的引用进行拆箱,将会抛出InvalidCastException异常。
【例2-9】创建一个C#控制台应用程序,说明如何通过拆箱操作把对象引用类型转换为值类型,程序运行结果如图2-12所示。
图2-12 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-09”。保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,并在Main方法中添加以下代码:
int i = 123; // 声明值类型变量并进行初始化 object o = i; // 对变量i装箱,将值类型隐式转换为对象类型 int j = (int)o; // 对变量o拆箱,将对象引用类型显式转换为值类型 i = 456; Console.WriteLine("i = {0}", i); Console.WriteLine("o = {0}", o); Console.WriteLine("j = {0}\n", j);
(3)按Ctrl+F5组合键编译并运行程序。
2.3 变量和常量
使用C#执行计算时通常需要存储数据,例如,可能需要比较一些值并根据比较结果来执行不同操作。要比较这些值,就需要保留它们。在C#中使用变量来存储数据。变量表示数值或字符串值或类的对象。变量存储的值可能会发生更改,但名称保持不变。变量是字段的一种类型,字段的另一种类型是常量。常量保存在编译程序时赋予的值,并且此后在任何情况下都不会发生更改。
2.3.1 声明和使用变量
变量对应于计算机内存中存储单元,在C#中可以通过变量来存储和访问数据。变量应当声明后使用,否则编译器会检测出这个错误。
通过声明变量可以为其指定数据类型和名称,可以根据数据类型为变量分配存储空间,并允许通过变量名来访问在内存单元中存储的数据。在C#中,声明一个或多个变量的语法格式如下:
datatype variablename1[ = initializer1 ], variablename2[ = initializer2 ], . . .;
其中datatype指定变量的数据类型,可以是前面介绍过的各种值类型和引用类型。
variablename指定变量的名称,命名变量时必须符合标识符命名规则。而且变量名最好要具有实际意义,以便能做到见名知意。通常使用一个或多个英文单词以大小写混合形式组成标识符,标识符的首字母采用小写形式,后面连接的每个单词的首字母都采用大写形式,例如userName及password等。
initializer为可选项,是声明变量时计算并赋值给变量的表达式,在声明变量时为其赋值即变量的初始化。例如,在下面的语句中声明了4个变量,并初始化其中的两个变量:
int quantity; decimal price = 28.00m; string isbn, bookTitle = "Visual C# 程序设计";
在C#中每种值类型都有一个默认的构造函数,可以用来返回此值类型的默认值,如表2-6所示。
表2-6 值类型的默认值
默认构造函数通过new运算符来调用,例如:
int myInt = new int();
这个语句等效于下面的语句:
int myInt = 0;
在C#中不允许使用未初始化的变量。如果在声明一个变量时未设置其初始值,则使用其值之前必须通过赋值语句指定该变量的值。赋值语句的语法格式如下:
variablename = expression;
其中variablename为变量名,expression为表达式。=为赋值运算符,其作用是将右操作数的值存储在左操作数表示的变量中并将值作为结果返回。除了变量以外,左操作数也可以是属性或索引器。使用赋值运算符时,要求其两侧操作数的类型必须相同,或右边的操作数必须可以隐式转换为左边操作数的类型。
例如,在下面的语句中声明了两个变量,然后使用赋值语句为其赋值。对于已初始化或已赋值的变量,在代码中可以通过变量名来访问存储在变量中的信息:
int i; double x; i = 123; x = 456.789;
2.3.2 声明和使用常量
常量是在编译时设置其值并且永远不能更改其值的字段,使用常量可以为特殊值提供有意义的名称以代替抽象的数字或文本。
在C#中,常量使用const关键字声明,声明常量时需要指定常量的名称和数据类型并初始化。例如,下面的语句声明了3个double常量:
public const double x = 1.0, y = 2.0, z = 3.0;
常量的数据类型可以是任何一种值类型,也可以是引用类型。当使用引用类型时,常量的值只能是字符串或null值。
在C#中定义常量一般采用以下两种方式。
(1)若要定义整数类型(int及byte等)的常量值,可以使用枚举类型。
(2)若要定义非整型常量,可以将其分组到单个名为“Constants”的静态类中。定义静态类时应在class关键字前面添加static,引用常量时必须使用该类名作为限定符,这将有助于确保开发人员知道它是常量并且不能修改。
与C和C++不同,在C#中不能使用#define预处理器指令定义常量。
【例2-10】创建一个C#控制台应用程序,说明如何通过静态类来定义非整型常量,程序运行结果如图2-13所示。
图2-13 程序运行结果
【设计步骤】
(1)在解决方案chapter02中新建一个控制台应用程序项目,项目名称为“ConsoleApplication2-10”。保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为当前解决方案的启动项目。
(2)在代码编辑器中打开源文件Program.cs,在Program类定义上方声明一个静态类Contants,代码如下:
static class Contants { public const string Title = "常量应用示例"; public const double Pi =3.1415926; }
(3)在Program类的Main方法中编写以下代码:
Console.WriteLine(Contants.Title); // 引用常量Contants.Title double radius = 6; double area = Contants.Pi * radius * radius; // 引用常量Contants.Pi Console.WriteLine("当半径 = {0}时,圆面积 = {1}\n",radius, area);
(4)按Ctrl+F5组合键编译并运行程序。
2.4 运算符
运算符与运算对象一起组成表达式,运算符指定在表达式中执行的操作,其对象包括常量、变量和函数等。C#提供了大量的运算符,其中有很多运算符还可以被用户重载,从而在应用到用户定义的类型时更改这些运算符的含义。C#中的运算符可以分为算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符,以及其他运算符。
2.4.1 算术运算符
C#语言提供了如下5种算术运算符。
(1)+运算符:既可以作为一元运算符,也可以作为二元运算符。
● 作为一元运算符,为所有数值类型预定义。其作用相当于正号,即取操作数的值。
● 作为二元运算符,为数值类型和字符串类型预定义。对于数值类型,+运算符计算两个操作数之和;如果有一个操作数或两个操作数都是字符串类型,则把操作数的字符串表示形式串联在一起。
(2)-运算符:既可以作为一元运算符,也可以作为二元运算符。
● 作为一元运算符,为所有数值类型预定义,功能为取操作数的相反数。
● 作为二元运算符,为所有数值类型和枚举类型预定义,功能为从第1个操作数中减去第2个操作数。
(3)* 乘法运算符:计算两个操作数的乘积。
(4)/ 除法运算符:用第2 个操作数除第1 个操作数,所有数值类型都具有预定义的除法运算符,两个整数相除的结果始终为一个整数。例如,5除以2的结果为2。若要获取作为有理数或分数的商,应将被除数或除数设置为float类型或double类型,可通过在数字后添加一个小数点来隐式执行此操作。
(5)% 模数运算符:计算第2个操作数除第1个操作数后的余数,所有数值类型都具有预定义的模数运算符。
【例2-11】创建一个C#控制台应用程序,说明如何使用算术运算符执行各种算术运算,程序运行结果如图2-14所示。
图2-14 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-11”。保存在F:\Visual C sharp 2008\chapter02文件夹中,并将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在Main方法中添加以下代码:
int a = 5; Console.Write("执行+运算:\t"); Console.Write(+a); Console.Write('\t'); Console.Write(a + 5); Console.Write('\t'); Console.Write(a + .5); Console.Write('\t'); Console.Write("5" + "5"); Console.Write('\t'); Console.WriteLine(5.0 + "5"); Console.Write("执行-运算:\t"); Console.Write(-a); Console.Write('\t'); Console.Write(a -1); Console.Write('\t'); Console.WriteLine(a - .5); Console.Write("执行*运算:\t"); Console.Write(a * 2); Console.Write('\t'); Console.Write(-.5 * .2); Console.Write('\t'); Console.WriteLine(-.5m * .2m); Console.Write("执行/运算:\t"); Console.Write(a / 2); Console.Write('\t'); Console.Write(a / 2.1); Console.Write('\t'); Console.Write(5.1 / 2); Console.Write('\t'); Console.WriteLine(-a / 2); Console.Write("执行%运算:\t"); Console.Write(a % 2); Console.Write('\t'); Console.WriteLine(5.1 % 2.1); Console.WriteLine();
(3)按Ctrl+F5组合键编译并运行程序。
2.4.2 关系运算符
关系运算符用于比较两个操作数并返回一个bool值,其中包括如下运算符。
(1)小于运算符(<):应用于所有数值和枚举类型,如果第1个操作数小于第2个操作数,则返回true;否则返回false。
(2)大于运算符(>):应用于所有数值和枚举类型,如果第1个操作数大于第2个操作数,则返回true;否则返回false。
(3)相等运算符(==):应用于值类型和引用类型。
● 对于预定义的值类型,如果操作数的值相等,则返回true;否则返回false。
● 对于string引用类型,用于比较字符串的值。
● 对于string以外的引用类型,如果两个操作数引用同一个对象,则返回true;否则返回false。
(4)不等运算符(!=):如果操作数相等,则返回false;否则返回true。为所有类型(包括字符串和对象)预定义了不等运算符。
● 对于预定义的值类型,如果操作数的值不同,则返回true;否则返回false。
● 对于string引用类型,比较字符串的值。
● 对于string以外的引用类型,如果两个操作数引用不同的对象,则返回true;否则返回false。
(5)小于等于运算符(<=):应用于所有数值和枚举类型,如果第1个操作数小于或等于第2个操作数,则将返回true;否则返回false。
(6)大于等于运算符(>=):应用于所有数值和枚举类型,如果第1个操作数大于或等于第2个操作数,则将返回true;否则返回false。
【例2-12】创建一个C#控制台应用程序,说明如何使用关系运算符执行各种关系运算,程序运行结果如图2-15所示。
图2-15 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-12”。保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在Main方法中添加以下代码:
object obj1 = new object(); object obj2 = new object(); Console.Write("1 < 2 = {0}\t",1 < 2); Console.WriteLine("1 > 2 = {0}", 1 > 2); Console.Write("1 == 2 = {0}\t", 1 == 2); Console.WriteLine("2 == 2 = {0}", 2 == 2); Console.Write("\"abc\" == \"aaa\" = {0}\t", "abc" == "aaa"); Console.WriteLine("\"abc\" != \"aaa\" = {0}", "abc" != "aaa"); Console.Write("obj1 == obj2 = {0}\t", obj1 == obj2); Console.WriteLine("obj1 != obj2 = {0}", obj1 != obj2); Console.Write("1 <= 2 = {0}\t", 1 <= 2); Console.WriteLine("2 <= 2 = {0}", 2 <= 2); Console.Write("3 >= 2 = {0}\t", 3 >= 2); Console.WriteLine("3 >= 3 = {0}\n", 3 >= 3);
(3)按Ctrl+F5组合键编译并运行程序。
2.4.3 逻辑运算符
逻辑运算符用于bool类型操作数,其中包括如下运算符。
(1)逻辑非运算符(!):这是一个一元运算符,用于对操作数求反。为bool定义了该运算符,当且仅当操作数为false时,返回true;否则返回false。
(2)逻辑与运算符:包括&&和&。
● &&对bool操作数执行逻辑与运算,仅在必要时才计算第2个操作数。对于表达式x && y,如果x为false,则不计算y。因为不论y为何值,“与”操作的结果都是false,这称作为“短路”计算。&&运算符仅在第1个操作数为true时才计算第2个操作数。
● &对bool操作数执行逻辑与运算,当且仅当两个操作数均为true时,结果才为true;若两个操作数中有一个为false,则返回false。不论第1个操作数取何值,&运算符都会对两个操作数执行计算。
(3)逻辑或运算符:包括 || 和 |。
● || 对bool操作数执行逻辑或运算,但仅在必要时才计算第2个操作数。对于表达式x || y,如果x为true,则不计算y。因为不论y为何值,“或”操作的结果都是true,这称为“短路”计算。|| 运算符仅在第1个操作数为false时才计算第2个操作数。
● | 对bool操作数执行逻辑或运算,当且仅当两个操作数均为false时,结果才为false;若两个操作数中有一个为true,则返回true。不论第1个操作数取何值,| 运算符都会对两个操作数执行计算。
(4)逻辑异或运算符(^):计算bool操作数的逻辑异或运算,即当且仅当只有一个操作数为true时,结果才为true;如果两个操作数同时为true或false,则结果为false。
提示
&运算符既可以作为一元运算符,也可以作为二元运算符。一元&运算符用于返回操作数的地址,要求不安全上下文;作为二元运算符,&运算符还可以对整型操作数执行按位与运算。与此类似,^运算符也可以对整型操作数执行按位异或运算。
使用逻辑运算符&&和&、|| 和 | 时计算结果虽然相同,但在某些情况下由于使用&&或 || 运算符执行了“短路”计算,因此有助于提高性能。
【例2-13】创建一个C#控制台应用程序,说明如何使用逻辑运算符执行各种逻辑运算,程序运行结果如图2-16所示。
图2-16 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-13”。保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在Main方法中添加以下代码:
Console.Write("!(2 > 3) = {0}\t", !(2 > 3)); Console.WriteLine("!(3 > 2) = {0}\t", !(3 > 2)); Console.Write("2 > 3 && 6 < 3 = {0}\t", 2 > 3 && 6 < 3); Console.WriteLine("2 > 3 & 6 < 3 = {0}\t", 2 > 3 & 6 < 3); Console.Write("3 > 2 && 6 > 3 = {0}\t", 3 > 2 && 6 > 3); Console.WriteLine("3 > 2 & 6 > 3 = {0}\t", 3 > 2 & 6 > 3); Console.Write("2 > 3 || 6 < 3 = {0}\t", 2 > 3 || 6 < 3); Console.WriteLine("2 > 3 | 6 < 3 = {0}\t", 2 > 3 | 6 < 3); Console.Write("3 > 2 || 6 > 3 = {0}\t", 3 > 2 || 6 > 3); Console.WriteLine("3 > 2 | 6 > 3 = {0}\t", 3 > 2 | 6 > 3); Console.Write("3 > 2 ^ 3 > 6 = {0}\t", 3 > 2 ^ 3 > 6); Console.WriteLine("3 > 2 ^ 6 > 3 = {0}\n", 3 > 2 ^ 6 > 3);
(3)按Ctrl+F5组合键编译并运行程序。
2.4.4 位运算符
如下位运算符对二进制位执行操作。
(1)按位求补运算符(~):用于对操作数执行按位求补运算,其效果相当于反转操作数中的每个二进制位。即0变为1,1变为0。该运算符是为int、uint、long和ulong类型预定义的。
(2)按位与运算符(&):对两个整型操作数执行按位与运算符,如果两个操作数中的对应位同时为1,则结果的对应位为1;如果两个操作数的对应位中有一个为0,则结果的对应为0。
(3)按位或运算符(|):对两个整型操作数执行按位或运算符,只要两个操作数中的对应位有一个为1,则结果的对应位为1;如果两个操作数的对应位同时为0,则结果的对应为0。
(4)按位异或运算符(^):对两个整型操作数执行按位异或运算,当且仅当操作数中对应位中有一个为1时,结果的对应位也是1;如果操作数的对应位同时为0或同时为1,则结果的对应位为0。
(5)左移运算符(<<):将第1个操作数向左移动第2个操作数指定的位数,第1个操作数的高序位被放弃,低序空位用0填充。移位操作从不导致溢出,第2个操作数的类型必须是int。
● 如果第1个操作数是int或uint(32位数),则移位数由第2个操作数的低5位(第2个操作数 &0x1f)给出。
● 如果第1个操作数是long或ulong(64位数),则移位数由第2个操作数的低6位(第2个操作数 & 0x3f)给出。
(6)右移运算符(>>):将第1个操作数向右移动第2个操作数所指定的位数。
● 如果第1个操作数为int或uint(32位数),则移位数由第2个操作数的低5位给出(第2个操作数 & 0x1f)。
● 如果第1个操作数为long或ulong(64位数),则移位数由第2个操作数的低6位给出(第2个操作数 & 0x3f)。
● 如果第1个操作数为int或long,则右移位是算术移位,即高序空位设置为符号位,正数用0表示,负数用1表示。
● 如果第1个操作数为uint或ulong类型,则右移位是逻辑移位,即高位填充0。
【例2-14】创建一个C#控制台应用程序,说明如何使用位运算符执行各种位运算,程序运行结果如图2-17所示。
图2-17 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-14”。保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在Main方法中添加以下代码:
int i = 0xb5; // 10110101 int j = 0xe6; // 11100110 Console.WriteLine("~{0:x}={1:x}\t~{2:x}={3:x}",i,~i,j,~j); // x为格式字符,表示使用十六进制格式 Console.Write("{0:x} & {1:x} = {2:x}\t", i, j, i & j); // 10110101 & 11100110 = 10100100 Console.WriteLine("{0:x} | {1:x} = {2:x}\t", i, j, i | j); // 10110101 |11100110 = 11110111 Console.Write("{0:x} ^ {1:x} = {2:x}\t", i, j, i ^ j); // 10110101 ^ 11100110 = 1010011 Console.WriteLine("{0:x} << 3 = {1:x}", i,i << 3); // 10110101 << 3 = 10110101000 Console.WriteLine("{0:x} >> 3 = {1:x}\n", i, i >> 3); // 10110101 >> 3 = 10110
(3)按Ctrl+F5组合键编译并运行程序。
2.4.5 赋值运算符
如前所述,赋值运算符(=)的作用是将右操作数(可以是常量、变量或表达式)的值存储在左操作数表示的变量、属性或索引器中,并将该值作为结果返回。除了基本的赋值运算符(=)以外,还有如下多种复合赋值运算符。
(1)加法赋值运算符(+=):将变量的值与表达式的值相加(或串联)并将结果赋给变量,例如,x +=y等效于x = x + y。其中+运算符的作用取决于x和y的类型,对于数值操作数,其作用是加法;对于字符串操作数,其作用是串联。
(2)减法赋值运算符(-=):从变量的值减去表达式的值并将结果赋给变量,例如,x -= y等效于x = x - y。
(3)乘法赋值运算符(*=):将变量的值与表达式的值相乘并将结果赋给变量,例如,x *= y等效于x = x * y。
(4)除法赋值运算符(/=):将变量的值除以表达式的值并将结果赋给变量,例如,x /= y等效于x = x / y。
(5)模数赋值运算符(%=):将变量的值除以表达式的值所得的余数赋给变量,例如,x % = y等效于x = x % y。
(6)“与”赋值运算符(&=):将变量的值与表达式的值进行按位与或逻辑与并将结果赋给变量,例如x &= y等效于x = x & y。
(7)“或”赋值运算符(|=):将变量的值与表达式的值进行按位或逻辑或并将结果赋给变量,例如x |=y等效于x = x | y。
(8)“异或”赋值运算符(^=):将变量的值与表达式的值进行按位异或逻辑异或并将结果赋给变量,例如x ^= y等效于x = x ^ y。
(9)左移赋值运算符(<<=):将变量的值按表达式的值执行左移运算并将结果赋给变量,例如x <<= y等效于x = x << y。
(10)右移赋值运算符(>>=):将变量的值按表达式的值执行右移运算并将结果赋给变量,例如x >>=y等效于x = x >> y
【例2-15】创建一个C#控制台应用程序,说明如何使用各种赋值运算符执行运算,程序运行结果如图2-18所示。
图2-18 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-15”。保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在Main方法中添加以下代码:
int x = 3; int y = 5; Console.WriteLine("x = {0}\t y = {1}",x,y); Console.Write("执行x += y后:x = {0}\t", x += y); Console.WriteLine("执行x -= y后:x = {0}", x -= y); Console.Write("执行x *= y后:x = {0}\t", x *= y); Console.WriteLine("执行x /= y后:x = {0}", x /= y); Console.Write("执行x %= y后:x = {0}\t", x %= y); Console.WriteLine("执行x &= y后:x = {0}", x -= y); Console.Write("执行x -= y后:x = {0}\t", x &= y); Console.WriteLine("执行x |= y后:x = {0}", x|= y); Console.Write("执行x ^= 1后:x = {0}\t", x ^= 1); Console.WriteLine("执行x <<= y后:x = {0}", x <<= y); Console.WriteLine("执行x >>= 3后:x = {0}\n", x >>= 3);
(3)按Ctrl+F5组合键编译并运行程序。
2.4.6 其他运算符
除了前面已经讨论的算术运算符、关系运算符、逻辑运算符、位运算符及赋值运算符以外,C#中还有如下运算符。
(1)方括号运算符([]):用于数组、索引器和属性,也可用于指针。例如,声明数组和引用数组元素时都要用到[]运算符:
int array1 = new int[100]; array[1] = 123;
(2)圆括号运算符(()):用于指定表达式中的运算顺序外,或指定强制转换或类型转换。例如:
double d = 123.456; int i = (int)d;
(3)成员访问运算符(.):用于成员访问,指定类型或命名空间的成员。例如,用于访问 .NET Framework类库中的特定方法:
System.Console.WriteLine("Hello!");
(4)命名空间别名限定符(::):用于查找标识符,通常放置在两个标识符之间,例如:
global::System.Console.WriteLine("Hello World");
命名空间别名限定符可以是global,即调用全局命名空间,而不是在别名命名空间中的查找。
(5)条件运算符(?:):根据布尔型表达式的值返回两个值中的一个,其格式如下:
condition ? first expression : second expression;
如果条件为true,则计算第1个表达式并以其计算结果为准。如果条件为false,则计算第2个表达式并以其计算结果为准。只计算两个表达式中的一个。
(6)增量运算符(++):应用于数值类型和枚举类型,功能是将操作数加1。该运算符可以出现在操作数之前或之后,作用有所不同。
● 前缀增量操作:该运算的结果是操作数加1之后的值,例如++x表示使变量x的值加1,然后返回变量x的值。
● 后缀增量操作:该运算的结果是操作数增加之前的值,例如x++表示返回变量x的值,然后使变量x的值加1。
(7)减量运算符(--):应用于数值类型和枚举类型,功能是将操作数减1。该运算符可以出现在操作数之前或之后,作用有所不同。
● 前缀减量操作:该运算的结果是操作数减1之后的值,例如--x表示先使变量x的值减1,然后返回变量x的值。
● 后缀减量操作:该运算的结果是操作数减1之前的值,例如x--表示先返回变量x的值,然后使变量x的值减1。
(8)运算符->:将指针取消引用与成员访问组合在一起,例如x->y。其中x为T*类型的指针,y为等效于(*x).y。
(9)双问号运算符(??):若??运算符的左操作数非null,则返回左操作数;否则返回右操作数。
(10)lambda运算符(=>):在lambda表达式中用来将左侧的输入变量与右侧的lambda体分离。
(11)is运算符:检查对象是否与给定类型兼容,即判断某个变量或表达式是否为给定的数据类型,其语法格式如下:
expression is dataType
如果表达式expression非空,并且所提供的对象可以强制转换为给定的类型,而不会导致引发异常,则is表达式的计算结果为true;如果已知表达式将始终是true或false,则is关键字将导致编译时警告。通常在运行时才计算类型兼容性,例如使用下面的代码可确定对象是否与string类型兼容:
if (obj is string) { Console.WriteLine("obj是字符串。"); }
(12)as运算符:在兼容的引用类型之间执行转换,可用于检查表达式的数据类型,其语法格式如下:
expression as type
其中expression为引用类型(如object及string)的变量或表达式。如果expression的值符合给定的数据类型type,则计算结果为expression的值;否则为null值。expression as type相当于以下条件表达式:
expression is type ? (type)expression : (type)null
例如,使用下面的代码可以检查someObject的数据类型:
string s = someObject as string; if (s != null) { Console.WriteLine("someObject是字符串。"); }
as运算符类似于强制转换操作,但是如果无法转换,则as返回null,而不引发异常。该运算符只执行引用转换和装箱转换,无法执行其他转换(如用户定义的转换),这类转换应使用强制转换表达式来执行。
(13)new运算符:用于创建对象和调用构造函数,例如:
Class1 o = new Class1(); int[] array = new [100]; int i = new int();
(14)typeof运算符:用于获取类型的System.Type对象,例如:
System.Type type = typeof(int);
若要获取表达式的运行时类型,可以使用 .NET Framework的方法GetType,例如:
int i = 0; System.Type type = i.GetType();
(15)sizeof运算符:用于获取值类型的字节大小,例如可以用下面的语句来检索int类型的大小:
int intSize = sizeof(int);
(16)checked运算符:用于对整型算术运算和显式转换启用溢出检查,其语法格式如下:
checked(expression)
默认情况下,如果表达式产生的值超出了目标类型的范围,则常数表达式将导致编译时错误;非常数表达式在运行时计算并将引发异常。如果通过编译器选项或环境配置在全局范围内取消了溢出检查,则可以使用checked关键字来启用此项功能。
(17)unchecked运算符:用于取消整型算术运算和转换的溢出检查,其语法格式如下:
unchecked(expression)
在未检查的上下文中,如果表达式产生目标类型范围之外的值,则计算结果将被截断。
【例2-16】创建一个C#控制台应用程序,说明如何使用增量、减量及条件运算符,程序运行结果如图2-19所示。
图2-19 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-16”。保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在Main方法中添加以下代码:
int a = 2; int b = 3; Console.WriteLine("执行增量和减量后:a = {0}\tb = {1}", a, b); Console.Write("执行前缀增量++a:{0}\t", ++a); Console.WriteLine("执行后缀增量a++:{0}", a++); Console.Write("执行前缀减量--b:{0}\t", --b); Console.WriteLine("执行后缀减量b--:{0}", b--); Console.WriteLine("执行增量和减量后:a = {0}\tb = {1}", a, b); Console.WriteLine("使用条件运算符:a > b ? a : b = {0}\n", a > b ? a : b);
(3)按Ctrl+F5组合键编译并运行程序。
【例2-17】创建一个C#控制台应用程序,说明如何使用is、as、typeof及sizeof运算符,程序运行结果如图2-20所示。
图2-20 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-17”。保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在Main方法中添加以下代码:
object obj1 = "hi"; Console.Write("{0}\t",obj1 is string); obj1 = 123; // 以下是is运算符应用示例 Console.Write("{0}\t", obj1 is int); Console.WriteLine(obj1.GetType()); Console.Write("{0}\t", 123 is int); // 导致编译时警告 Console.Write("{0}\t", 123 is long); // 导致编译时警告 Console.Write("{0}\t", 1.23 is float); // 导致编译时警告 Console.WriteLine("{0}", 1.23 is double); // 导致编译时警告 // 以下是as运算符应用示例 Console.Write("{0}\t", obj1 as object); object obj2 = (obj1 as string == null) ? "空值" : (obj1 as string); Console.WriteLine("{0}", obj2 as string); // 以下是typeof运算符应用示例 Console.Write("{0}\t", typeof(int)); Console.WriteLine("{0}\t", typeof(uint)); Console.Write("{0}\t", typeof(object)); Console.WriteLine("{0}", typeof(string)); // 以下是sizeof运算符应用示例 Console.Write("{0}\t", sizeof(int)); Console.Write("{0}\t", sizeof(long)); Console.Write("{0}\t", sizeof(float)); Console.WriteLine("{0}", sizeof(double));
(3)按Ctrl+F5组合键编译并运行程序。
2.4.7 运算符优先级
每种运算符都有一定的优先级,决定了它在表达式中的运算次序。在同一个表达式中,优先级高的运算符先于优先级低的运算符执行;优先级相同的按结合性从左向右或从右向左的顺序执行。根据需要也可以使用圆括号来改变运算顺序,即首先计算圆括号内的表达式,然后计算圆括号外的表达式。表2-6所示为C#运算符按优先级从高到低的顺序排列。
表2-6 运算符优先级
表2-6中的每一行为一组,每个组中的运算符具有相同的优先级。
如果具有同一优先级的运算符在表达式中左右相邻,则要根据运算符的结合性来决定运算顺序。该结合性分为左结合和右结合,左结合表示标识符优先与其左边的运算符结合执行运算;右结合表示标识符优先与其右边的运算符结合执行运算。
除了赋值运算符,所有双目运算符都具有左结合性,赋值运算符和条件运算符都具有右结合性。根据结合性规则,x+y+z按(x+y)+z执行计算,x=y=z则按x=(y=z)执行计算。
当使用算术运算符(+、-、*、/)时,计算的结果可能会超出涉及的数值类型可能值的范围,称为“算术溢出”。整数算术溢出或者引发OverflowException,或者丢弃结果的最高有效位。
整数被零除总是引发DivideByZeroException;浮点算术溢出或被零除从不引发异常,因为浮点类型基于IEEE 754,因此可以用来表示无穷和NaN(不是数字);小数算术溢出总是引发OverflowException,小数被零除总是引发DivideByZeroException。
当发生整数溢出时,产生的结果取决于执行上下文,该上下文可为checked或unchecked。在checked上下文中引发OverflowException,在未选中的上下文中放弃结果的最高有效位并继续执行,因此C#使开发人员有机会选择处理或忽略溢出。
除算术运算符以外,整型数据之间的强制转换也会导致溢出(例如将long强制转换为int时)并受checked或unchecked执行的限制。不过,按位运算符和移位运算符永远不会导致溢出。
2.5 控制台输入/输出
控制台是在操作系统中打开的一个窗口,用户可以在其中通过计算机键盘输入文本并从计算机终端读取文本输出,从而与操作系统或基于文本的控制台应用程序进行交互。在Windows操作系统平台上,控制台称为“命令提示窗口”,可以用来接收MS-DOS命令。Console类为从控制台读取字符并向控制台写入字符的应用程序提供基本支持。
2.5.1 屏幕缓冲区与控制台窗口
控制台本身是一个操作系统窗口,屏幕缓冲区和控制台窗口是控制台的两个功能,二者之间关系紧密。文本实际上从控制台拥有的输入流和输出流读取和写入,但看起来像是在控制台的一个区域中读取和写入的,该区域称为“屏幕缓冲区”。屏幕缓冲区是控制台的一个属性,以由行和列组成的矩形网格的形式组织,网格中的每个交叉位置称为“一个字符单元格”。其中可以包含一个字符,每个字符单元格都有自己的背景色和前景色。
屏幕缓冲区可以通过一个矩形区域来查看,该区域称为“控制台窗口”。控制台窗口并不是控制台本身,而是控制台的一个属性。它也是以行和列的形式组织的,其大小与屏幕缓冲区相同或者更小,该窗口可以移动以查看屏幕缓冲区的不同区域。如果屏幕缓冲区比控制台窗口大,控制台会自动显示滚动条,以使控制台窗口可以在屏幕缓冲区中重新定位。
光标指示屏幕缓冲区中当前正在读取或写入文本的位置,根据需要也可以隐藏或显示光标,并设置其高度。如果光标可见,控制台窗口的位置会自动移动,以便总是能够看到光标。
字符单元格在屏幕缓冲区中的坐标原点为左上角,光标和控制台窗口的位置相对于该原点计算。位置通过从零开始的索引指定,将最上面的行指定为行0,将最左边的列指定为列0。行索引和列索引的最大值为Int16.MaxValue。
2.5.2 Console类的成员
Console类位于命名空间System中,它表示控制台应用程序的标准输入流、输出流和错误流,无法继承此类。该类是一个静态类,访问其方法和属性成员时不需要声明类的实例。
1. Console类的常用方法
Console类的常用方法如下。
(1)Beep:通过控制台扬声器播放提示音。
(2)Clear:清除控制台缓冲区和相应控制台窗口的显示信息。
(3)MoveBufferArea:将屏幕缓冲区的指定源区域复制到指定的目标区域。
(4)Read:从标准输入流读取下一个字符。
(5)ReadKey:获取用户按下的下一个字符或功能键。
(6)ReadLine:从标准输入流读取下一行字符。
(7)ResetColor:将控制台的前景色和背景色设置为默认值。
(8)SetBufferSize:将屏幕缓冲区的高度和宽度设置为指定值。
(9)SetWindowPosition:设置控制台窗口相对于屏幕缓冲区的位置。
(10)SetWindowSize:将控制台窗口的高度和宽度设置为指定值。
(11)Write:将指定值的文本表示形式写入标准输出流。
(12)WriteLine:将指定的数据(后跟当前行终止符)写入标准输出流。
2. Console类的常用属性
Console类的常用属性如下。
(1)BackgroundColor:获取或设置控制台的背景色。
(2)BufferHeight:获取或设置缓冲区的高度。
(3)BufferWidth:获取或设置缓冲区的宽度。
(4)CapsLock:获取一个值,用于指示Caps Lock键盘切换键是打开,还是关闭的。
(5)CursorLeft:获取或设置光标在缓冲区中的列位置。
(6)CursorSize:获取或设置光标在字符单元格中的高度。
(7)CursorTop:获取或设置光标在缓冲区中的行位置。
(8)CursorVisible:获取或设置一个值,用于指示光标是否可见。
(9)KeyAvailable:获取一个值,用于指示按键操作在输入流中是否可用。
(10)LargestWindowHeight:根据当前字体和屏幕分辨率获取控制台窗口可能具有的最大行数。
(11)LargestWindowWidth:根据当前字体和屏幕分辨率获取控制台窗口可能具有的最大列数。
(12)NumberLock:获取一个值,用于指示Num Lock键盘切换键是打开,还是关闭的。
(13)Title:获取或设置要显示在控制台标题栏中的标题。
(14)WindowHeight:获取或设置控制台窗口区域的高度。
(15)WindowLeft:获取或设置控制台窗口区域的最左边相对于屏幕缓冲区的位置。
(16)WindowTop:获取或设置控制台窗口区域的最顶部相对于屏幕缓冲区的位置。
(17)WindowWidth:获取或设置控制台窗口的宽度。
2.5.3 控制台输入
创建C#控制台应用程序时,可以通过调用Console类的Read、ReadKey或ReadLine方法来获取用户输入的字符、功能键或字符串。
1. Read方法
Read方法用于从标准输入流读取下一个字符,其语法格式如下:
public static int Read()
调用Read方法有以下两种语法格式:
int i = Console.Read(); char ch = (char)Console.Read();
当执行调用Read方法的语句时,程序会暂停等待用户输入。如果用户输入任意字符并按下Enter键,该方法便从输入流中读取下一个字符;如果用户输入了多个字符并按Enter键,则该方法仅返回所输入的第1个字符,可以通过循环语句来读取输入的所有字符;如果当前输入流中没有字符可供读取,则返回-1。Read方法的返回值为int类型,若要获取输入的字符,可通过显示类型转换来实现。
【例2-18】创建一个C#控制台应用程序,说明如何通过Console类的Read方法来获取用户输入的字符,程序运行结果如图2-21所示。
图2-21 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-18”。保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在Main方法中添加以下代码:
Console.Title = "Console.Read方法应用示例"; Console.WindowHeight = 8; Console.WindowWidth = 60; Console.BackgroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.Black; Console.Clear(); Console.Write("请输入一个字符并按Enter键:"); int i = Console.Read(); char ch = (char)i; Console.WriteLine("您输入的字符是{0},其编码为{1}",ch,i); Console.WriteLine(); Console.Read(); Console.Read(); string str=""; Console.Write("请输入一个字符串(Enter=完成,X=退出):"); while (true) { ch = (char)Console.Read(); if (ch == 'x' || ch == 'X') return; str += ch; if (ch == '\n') break; } Console.WriteLine("您输入的字符串是:{0}", str);
(3)按Ctrl+F5组合键编译并运行程序。
2. ReadKey方法
ReadKey方法用于获取用户按下的下一个字符或功能键,其原型有以下两种形式:
public static ConsoleKeyInfo ReadKey() public static ConsoleKeyInfo ReadKey(bool intercept)
其中参数intercept为bool类型,用于指定是否在控制台窗口中显示按下的键。若为true,则表示不显示按下的键;否则显示。
调用ReadKey方法有以下两种语法格式:
ConsoleKeyInfo cki = ReadKey(); ConsoleKeyInfo cki = ReadKey(intercept); // 其中参数intercept的值为true或false
ReadKey方法的返回值为ConsoleKeyInfo为结构类型,ConsoleKeyInfo对象描述ConsoleKey常数和对应于按下的控制台键的Unicode字符(如果存在这样的字符),并以ConsoleModifiers值的按位组合描述是否在按下该控制台键的同时按下了Shift、Alt或Ctrl修改键中的一个或多个。
ConsoleKeyInfo对象包含以下属性。
(1)Key:获取当前ConsoleKeyInfo对象表示的控制台键。
(2)KeyChar:获取当前ConsoleKeyInfo对象表示的Unicode字符。
(3)Modifiers:获取System.ConsoleModifiers值的一个按位组合,指定与控制台键同时按下的一个或多个修改键。
【例2-19】创建一个C#控制台应用程序,说明如何通过Console类的ReadKey方法来获取用户输入的字符或功能键,程序运行结果如图2-22所示。
图2-22 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-19”。保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在Main方法中添加以下代码:
string str=""; Console.Title = "Console.ReadKey方法应用示例"; Console.BackgroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.Black; Console.Clear(); ConsoleKeyInfo cki; Console.Write("请按下一个字符键\n同时可按下Alt、Ctrl和Shift键:"); cki = Console.ReadKey(true); if ((cki.Modifiers & ConsoleModifiers.Alt) != 0) str += " Alt "; if ((cki.Modifiers & ConsoleModifiers.Control) != 0) str += " Ctrl "; if ((cki.Modifiers & ConsoleModifiers.Shift) != 0) str += " Shift "; Console.WriteLine(); Console.Write("您按了 {0} 键",cki.Key.ToString()); if(str!="")Console.WriteLine(",同时按了{0}键。",str); Console.WriteLine();
(3)按Ctrl+F5组合键编译并运行程序。
3. ReadLine方法
ReadLine方法用于从标准输入流读取下一行字符,其原型如下:
public static string ReadLine()
调用ReadLine方法的语法格式如下。
string str = Console.ReadLine();
当执行到调用ReadLine方法的语句时,程序暂停等待用户输入。当用户输入一个字符串并按Enter键后,ReadLine方法读取并返回输入流中的下一行字符。如果没有更多的可用行,则返回null引用。ReadLine方法返回后跟回车符(十六进制0x000d)、换行符(十六进制0x000a)或Environment.NewLine属性值的字符序列,但不包含终止字符,返回值为string类型。若要使用该方法输入数值数据,则需要对输入的字符串执行类型转换。
(1)输入整数:
string str = Console.ReadLine(); int i = Convert.ToInt32(str); // 或:int i = int.Parse(str);
(2)输入float:
string str = Console.ReadLine(); float f = Convert.ToFloat(str); // 或:float f = float.Parse(str);
(3)输入double:
string str = Console.ReadLine(); double d = Convert.ToDouble (str); // 或:double = double.Parse(str);
【例2-20】创建一个C#控制台应用程序,说明如何通过Console类的ReadLine方法来获取用户输入的字符串和数值数据,程序运行结果如图2-23所示。
图2-23 程序运行结果
【设计步骤】
(1)在解决方案chapter02中添加一个控制台应用程序项目,项目名称为“ConsoleApplication2-20”。保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在Main方法中添加以下代码:
Console.Title = "Console.ReadLine方法应用示例"; Console.BackgroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.DarkBlue; Console.Clear(); Console.Write("请输入一个字符串:"); string str = Console.ReadLine(); Console.WriteLine("您输入的字符串是:{0}",str); Console.Write("请输入一个整数:"); int i = int.Parse(Console.ReadLine()); Console.WriteLine("您输入的字符串是:{0}", i); Console.Write("请输入一个浮点数:"); float f = float.Parse(Console.ReadLine()); Console.WriteLine("您输入的浮点数是:{0}", f); Console.Write("请输入一个双精度浮点数:"); double d = double.Parse(Console.ReadLine()); Console.WriteLine("您输入的双精度浮点数是:{0}\n", d);
(3)按Ctrl+F5组合键编译并运行程序。
2.5.4 控制台输出
控制台输出可以通过Console类的Write和WriteLine方法实现,它们都可以将一个或多个数据的文本表示形式写入标准输出流。二者的区别在于Write不在数据后面添加回车换行符,WriteLine则添加。Write和WriteLine都有多种重载形式,不仅可以用于输出不同类型的数据,而且允许根据需要设置输出的格式。
控制台输出可以通过以下语法格式来实现:
Console.Write(format, arg0, arg1, ...); Console.WriteLine(format, arg0, arg1, ...);
其中format为复合格式字符串,参数arg0和arg1等表示要使用format写入的表达式。
格式字符串由零个或多个固定文本段与一个或多个格式项混合组成,其中固定文本是任意的字符串,每个格式项对应于参数列表中的一个表达式;复合格式设置功能返回新的结果字符串,其中每个格式项都被列表中相应表达式的字符串表示形式取代。每个格式项都采用下面的形式并包含以下组件:
{索引[,对齐][:格式字符串]}
下面说明格式项的组成。
(1)定界符:格式项必须使用成对的花括号“{”和“}”作为定界符。
(2)索引:也称为“参数说明符”,是一个从0开始的数字,可标识参数列表中对应的项,即参数说明符为0的格式项对应于列表中的第1个表达式,为1的格式项对于于列表中的第2个表达式,依此类推。通过指定相同的参数说明符,多个格式项可以引用参数列表中的同一个元素。例如,通过指定类似于“{0:X}{0:E} {0:N}”的复合格式字符串,可以将同一个数值设置为十六进制、科学计数法和数字格式。每个格式项都可以引用列表中的任一表达式。例如,如果有3个表达式,则可以通过指定类似于“{1} {0} {2}”的复合格式字符串来设置第2个、第1个和第3个表达式的格式。格式项未引用的表达式会被忽略。如果参数说明符指定了超出对象列表范围的项,将导致运行时异常。
(3)对齐:该组件是可选的,它一个带符号的整数,指示首选设置格式的字段宽度。如果“对齐”值小于设置了格式的字符串的长度,则“对齐”会被忽略,并且使用设置格式的字符串的长度作为字段宽度;如果“对齐”为正数,则字段中设置了格式的数据为右对齐;如果“对齐”为负数,则字段中的设置了格式的数据为左对齐。如果需要填充,则使用空白。如果指定“对齐”,则需要使用逗号。例如,“{0,6}”表示输出第1个参数,而且数据在6个字符宽的字段内右对齐;“{1,-8}”表示输出第2个参数,而且数据在8个字符宽的字段内左对齐。
(4)格式字符串:该组件也是可选的,用于指定正在设置格式的表达式类型的格式字符串,由一个格式说明符(字母)和一个精度说明符(数字)组成。如果相应表达式是数值,则指定数字格式字符串;如果是DateTime对象,则指定日期和时间格式字符串;如果是枚举值,则指定枚举格式字符串;如果未指定格式字符串,则对数字、日期和时间或者枚举类型使用常规格式说明符(“G”)。如果指定格式说明符,则需要使用冒号。标准格式说明符如表2-7所示。
表2-7 标准格式说明符
设置格式化输出时,应注意以下两个问题。
● 如果要设置格式的值是null,则返回空字符串("")。
● 由于在格式项中左花括号和右花括号被解释为格式项的开始和结束,因此必须使用转义序列显示文本左花括号或右花括号。在固定文本中指定两个左花括号("{{")来显示一个左花括号("{"),或指定两个右花括号("}}")来显示一个右花括号("}")。
【例2-21】创建一个C#控制台应用程序,说明如何通过Console类的WriteLine方法来实现数据的格式化输出,程序运行结果如图2-24所示。
图2-24 程序运行结果
【设计步骤】
(1)在Visual C#中打开解决方案chapter02,在其中添加一个控制台应用程序项目。项目名称为“ConsoleApplication2-21”,保存在F:\Visual C sharp 2008\chapter02文件夹中,将该项目设置为启动项目。
(2)在代码编辑器中打开源文件Program.cs,然后在Main方法中添加以下代码:
Console.Title = "格式化输出示例"; Console.BackgroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.Magenta; Console.Clear(); int i = 123, j = 456; Console.WriteLine("不设置格式:i = {0}\tj = {1}", i, j); Console.WriteLine("设置字段宽度为10且左对齐:i = {0,-10}", i); Console.WriteLine("设置字段宽度为10且右对齐:i = {0,10}", i); Console.WriteLine("设置为货币格式:i = {0,15:C}", i); Console.WriteLine("设置为浮点数:i = {0,12:F8}", i); Console.WriteLine("设置为科学计数法:i = {0,-18:E2}", i); Console.WriteLine("设置为百分比形式:i = {0:P6}", i); Console.WriteLine("设置为十六进制整数:i = {0:X}", i);
(3)按Ctrl+F5组合键编译并运行程序。