第5章 运算符
C#编程语言中包含大量的运算符,在日常的程序编写中,这些运算符会频繁地使用。通过前几章的代码示例,对一些运算符已经有所了解,本章将全面地、系统地介绍这些运算符。运算符最基本的目的是在表达式或者程序语句中进行变量、类实例等对象之间的赋值、计算、转换和判断。
5.1 使用as运算符
as运算符是一种类型转换符,用来在两个引用类型的对象之间进行类型转换,如果转换失败将转换为null,as运算符之后不能是值类型数据类型,即该运算符不支持值类型对象的转换,否则将会引起编译错误。as运算符支持值类型数据到Object类型的转换,这时对值类型变量进行的是一个装箱操作。
技术要点
本示例主要说明了使用as运算符转换引用类型的方法,技术要点如下。
● as运算符用于引用类型的转换和值类型的装箱。转换失败时,将转换为null,而不会引发异常。
● as运算符不能用于自定义的类型转换,当需要进行用户自定义的转换时,应使用cast来转换。
实现步骤
(1)创建控制台应用程序项目,命名为“AsExample”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; using System.Collections; namespace AsExample { class Program { static void Main(string[] args) { ArrayList myarray = new ArrayList();//创建一个动态数组 string asstring;//用来保存转换结果的字符串 myarray.Add("The first string.");//向数组中添加不同数据类型的元素 myarray.Add(23); myarray.Add("The second string."); myarray.Add(26); myarray.Add(41); myarray.Add("The third string."); foreach (Object obj in myarray)//使用foreach遍历数组 { //将Object类型转换为string类型,as操作符只能在两个引用类型之间进行 asstring = obj as string; if (asstring != null)//转换失败的结果是null,而不是空字符串 Console.WriteLine(obj as string); } Console.ReadLine(); } } }
(3)按F5键运行程序,运行结果如下所示。
The first string. The second string. The third string.
源程序解读
(1)本示例使用ArrayList类创建一个动态数组,在程序头部需要引用System.Collections命名空间。
(2)向ArrayList类的实例中可以添加不同类型的元素。在添加的过程中,值类型数据类型的元素装箱为引用类型的Object元素。
(3)使用foreach语句在ArrayList数组中遍历的时候,ArrayList中的每一个元素被当做Object类型的数据。在遍历过程中,使用as将Object的数组元素转换为string类型的元素,转换失败则为null,而并非空字符串。
5.2 使用is运算符
is运算符的作用是检查一个对象是否是指定的数据类型。如果该对象兼容指定的数据类型,包含is运算符的表达式返回结果true,如果该对象与指定的数据类型不兼容,包含is运算符的表达式返回结果false。
技术要点
本示例主要说明了is运算符的使用方法,技术要点如下。
● is运算符主要的作用是判断对象是否兼容的类型。在判断的过程中,先将判断对象转换为指定的数据类型,如果转换失败则返回false,不会抛出异常。
● 当子类b从父类a派生时,形如b类实例is a的表达式将返回true,而a类实例is b的表达式将返回false。
实现步骤
(1)创建控制台应用程序项目,命名为“IsExample”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace IsExample { class Program { static void Main(string[] args) { Person testpsn=new Person();//创建一个“人”类的实例 Employee testemp = new Employee();//创建一个员工类的实例 Department testdept = new Department();//创建一个部门类的实例 Console.WriteLine("testpsn类实例是:"); GetObjectType(testpsn); Console.WriteLine("-----------------"); Console.WriteLine("testemp类实例是:"); GetObjectType(testemp); Console.WriteLine("-----------------"); Console.WriteLine("testemp类实例的empdept属性是:"); GetObjectType(testemp.empdept); Console.WriteLine("-----------------"); Console.WriteLine("testdept类实例是:"); GetObjectType(testdept); Console.WriteLine("-----------------"); Console.ReadLine(); } static void GetObjectType(Object o) { if (o is Person)//判断对象是否“人”类 { Console.WriteLine("——“人”类"); } if (o is Employee)//判断对象是否员工类 { Console.WriteLine("——员工类"); } if (o is Department)//判断对象是否部门类 { Console.WriteLine("——部门类"); } } } class Person//表示“人”的类 { } class Employee : Person//表示员工的类 { public Department empdept = new Department(); } class Department//表示部门的类 { } }
(3)按F5键运行程序,运行结果如下所示。
testpsn类实例是: ——“人”类 ----------------- testemp类实例是: ——“人”类 ——员工类 ----------------- testemp类实例的empdept属性是: ——部门类 ----------------- testdept类实例是: ——部门类 -----------------
源程序解读
(1)本示例定义了三个类,并分别创建了三个类的实例,并定义了判断和显示类实例is运算的结果。示例的流程图如图5.1所示。
(2)本示例中Employee类继承于Person类,在进行is运算的时候,因为Employee类的实例testemp,对Person类使用is运算符判断的结果为true,所以testemp有两个输出。又因为Personl类的实例testpsn,对Employee类使用is运算符判断的结果为false,所以testpsn只有一个输出。
图5.1 is运算符示例流程
(3)本示例定义的GetObjectType方法,主要作用是判断传入的参数o,是示例中定义的Person类、Employee类、Department类中哪个类的实例。因为is运算符在比较时不会抛出异常,所以在该方法中不需要作异常判断。
5.3 使用new运算符
在C#编程语言中,new关键字的作用有三种,即作为运算符、修饰符和约束使用。本示例主要说明new关键字作为运算符的情况。new作为运算符使用时,主要作用是调用构造函数创建对象。
技术要点
本示例主要说明了如何使用new运算符创建结构和类的实例,技术要点如下。
● 在C#编程语言中,结构不允许定义不带参数的,即默认的构造函数,只能定义带参数的构造函数。类允许定义默认的构造函数,并能在默认的构造函数中为成员变量赋值。
● 类和结构都能定义多个构造函数,在使用new运算符创建类和结构的实例时,能够通过指定不同的参数列表,调用不同的构造函数创建实例。
● 使用new关键字创建数据类型对象时,对象具有一个默认值,不同的数据类型有各自的默认值,例如,int类型变量的默认值是0,string类型变量的默认值是null等。
实现步骤
(1)创建控制台应用程序项目,命名为“NewExample”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace NewExample { class Program { static void Main(string[] args) { //使用默认的构造函数来创建结构和类的实例 MyPoint point1 = new MyPoint(); Employee emp1 = new Employee(); Console.WriteLine("显示默认构造函数创建对象中的成员变量值"); Console.WriteLine("点结构的默认值:{0},{1}",point1.X,point1.Y); Console.WriteLine("员工类的默认值:FirstName={0},LastName={1},Age={2}", emp1.FirstName,emp1.LastName,emp1.Age); //使用带有参数的构造函数来创建结构和类的实例 MyPoint point2 = new MyPoint(10, 10); Employee emp2 = new Employee("Martin", "Smith", 23); Console.WriteLine("显示带有参数构造函数创建对象中的成员变量值"); Console.WriteLine("点结构的默认值:{0},{1}", point2.X, point2.Y); Console.WriteLine("员工类的默认值:FirstName={0},LastName={1},Age={2}", emp2.FirstName, emp2.LastName, emp2.Age); Console.ReadLine(); } struct MyPoint//表示点的结构 { public int X;//X坐标 public int Y;//Y坐标 public MyPoint(int x, int y)//点结构的构造函数,结构不能定义不带参数的构造函数 { X = x; Y = y; } } class Employee//表示员工的类 { public string FirstName; public string LastName; public int Age; public Employee()//定义默认的构造函数 { } //定义带有参数的构造函数,创建类实例的时候可以给类的成员变量赋值 public Employee(string firstname, string lastname, int age) { FirstName = firstname; LastName = lastname; Age = age; } } } }
(3)按F5键运行程序,运行结果如下所示。
显示默认构造函数创建对象中的成员变量值 点结构的默认值:0,0 员工类的默认值:FirstName=,LastName=,Age=0 显示带有参数构造函数创建对象中的成员变量值 点结构的默认值:10,10 员工类的默认值:FirstName=Martin,LastName=Smith,Age=23
源程序解读
(1)本示例定义了描述点的结构MyPoint和表示员工的类Employee,并分别定义各自的构造函数,其中MyPoint结构不能定义默认的即不带参数的构造函数。示例程序的流程如图5.2所示。
在图中,point1和point2是MyPoint结构的实例,分别由默认的构造函数和带参数的构造函数创建,其中默认的构造函数是不允许用户定义的。emp1和emp2是Employee类的实例,分别由默认的构造函数和带参数的构造函数创建,其中默认的构造函数是允许在类的内部自行定义的。
图5.2 new运算符示例程序的流程图
(2)在使用默认的构造函数创建对象时,如果没有对成员变量赋值,则成员变量保持初始的默认值。对于int类型的成员变量,默认的初始值为0,对于string类型的成员变量,默认的初始值为null,而不是空字符串。
5.4 使用sizeof运算符
sizeof运算符的作用是获取指定数据类型的字节数。在C#编程语言中,sizeof运算符只能用于值类型,一般是用于基本的数据类型。引用类型不能使用sizeof运算符。对于属于值类型的结构(struct)而言,sizeof运算符可用于不安全代码中。
技术要点
本示例主要说明了如何使用sizeof运算符获取值类型数据的字节数,技术要点如下。
● sizeof运算符只能用于值类型的数据类型,不能用于引用类型的数据类型。
● 在VS2008编程环境中,unsafe代码的编译必须使用/unsafe参数才能编译通过。具体设置为打开项目属性,将“生成”页签下的“允许不安全代码”选中,然后编译运行。
实现步骤
(1)创建控制台应用程序项目,命名为“SizeofExample”。
(2)单击菜单“项目”,选择“SizeofExample属性”,在“生成”页签中,勾选“允许不安全代码”。
(3)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace SizeofExample { class Program { static void Main(string[] args) { unsafe//表示不安全代码 { //获取结构占用的字节长度,这句代码必须放在unsafe声明的范围内 Console.WriteLine("MyPoint结构占用的字节数为:" + sizeof(MyPoint)); } //以下显示各种基本数据类型的sizeof运算结果 Console.WriteLine("sbyte数据类型的字节数为:" + sizeof(sbyte)); Console.WriteLine("byte数据类型的字节数为:" + sizeof(byte)); Console.WriteLine("short数据类型的字节数为:" + sizeof(short)); Console.WriteLine("ushort数据类型的字节数为:" + sizeof(ushort)); Console.WriteLine("int数据类型的字节数为:" + sizeof(int)); Console.WriteLine("uint数据类型的字节数为:" + sizeof(uint)); Console.WriteLine("long数据类型的字节数为:" + sizeof(long)); Console.WriteLine("ulong数据类型的字节数为:" + sizeof(ulong)); Console.WriteLine("char数据类型的字节数为:" + sizeof(char)); Console.WriteLine("float数据类型的字节数为:" + sizeof(float)); Console.WriteLine("double数据类型的字节数为:" + sizeof(double)); Console.WriteLine("bool数据类型的字节数为:" + sizeof(bool)); Console.ReadLine(); } struct MyPoint//表示点的结构 { public int X;//点的X坐标 public int Y;//点的Y坐标 } } }
(4)按F5键运行程序,运行结果如下所示。
MyPoint结构占用的字节数为:8 sbyte数据类型的字节数为:1 byte数据类型的字节数为:1 short数据类型的字节数为:2 ushort数据类型的字节数为:2 int数据类型的字节数为:4 uint数据类型的字节数为:4 long数据类型的字节数为:8 ulong数据类型的字节数为:8 char数据类型的字节数为:2 float数据类型的字节数为:4 double数据类型的字节数为:8 bool数据类型的字节数为:1
源程序解读
(1)本示例列出了各种数据类型占用的字节长度。其中对结构类型使用sizeof运算符需要放置在unsafe的代码段中。
(2)本示例包含了不安全代码,在编译的时候应选择允许不安全代码的选项,否则将引起编译失败。如果在命令行模式中编译,则编译命令如下。
csc Program.cs /unsafe
5.5 使用typeof运算符
typeof运算符的主要作用是获取指定数据类型的System.Type类的实例。typeof运算符的操作数是数据类型,而不是类实例或者变量。
技术要点
本示例主要说明了typeof运算符的使用方法,技术要点如下。
● typeof运算符的操作数是类或数据类型,而不是类的实例或者变量。
● typeof运算符的运算结果是一个Type类的实例,该实例表示typeof操作数对象的信息。通过使用Type类中定义的方法,能够获取类的信息,包括成员列表、字段列表、方法列表等。
实现步骤
(1)创建控制台应用程序项目,命名为“TypeofExample”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; using System.Reflection;//在程序中添加引用命名空间 namespace TypeofExample { class Program { static void Main(string[] args) { Type inttype = typeof(int); Console.WriteLine("类名称:" + inttype.FullName);//获取int数据类型的全名 //获取int数据类型的成员变量信息数组 MemberInfo[] intmembarr = inttype.GetMembers(); Console.WriteLine("成员变量:"); foreach (MemberInfo memb in intmembarr) { Console.WriteLine(memb.Name); } MethodInfo[] intmetharr = inttype.GetMethods();//获取int数据类型的方法信息数组 Console.WriteLine("方法:"); foreach (MemberInfo meth in intmetharr) { Console.WriteLine(meth.Name); } Console.WriteLine("——————————"); Type mytype = typeof(Employee);//获取员工类的实例对象 Console.WriteLine("类名称:" + mytype.FullName);//获取员工类的全名 FieldInfo[] fldbarr = mytype.GetFields();//获取员工类的定义字段信息数组 Console.WriteLine("成员变量:"); foreach (MemberInfo fld in fldbarr) { Console.WriteLine(fld.Name); } MethodInfo[] metharr = mytype.GetMethods();//获取员工类的方法信息数组 Console.WriteLine("方法:"); foreach (MemberInfo meth in metharr) { Console.WriteLine(meth.Name); } Console.ReadLine(); } } class Employee//表示员工的类 { public string FirstName; public string LastName; public int Age; public string GetName()//获得员工姓名的方法 { return FirstName + " " + LastName; } } }
(3)按F5键运行程序,运行结果如下所示。
类名称:System.Int32 成员变量: CompareTo Equals Equals GetHashCode ToString ToString ToString GetTypeCode CompareTo ToString Parse Parse Parse Parse TryParse TryParse GetType MaxValue MinValue 方法: CompareTo Equals Equals GetHashCode ToString ToString ToString GetTypeCode CompareTo ToString Parse Parse Parse Parse TryParse TryParse GetType ————— ———— 类名称:TypeofExample.Employee 成员变量: FirstName LastName Age 方法: GetName GetType ToString Equals GetHashCode
源程序解读
(1)本示例分别获取int数据类型和Employee类的Type类实例,通过该实例显示出这两个类型的信息。程序流程如图5.3所示。
图5.3 typeof运算符示例程序流程图
对于int数据类型的typeof运算,程序首先获取int数据类型的Type类实例inttype,然后获取inttype的成员数组,遍历该数组将成员名称显示出来,最后获取inttype的方法数组,遍历该方法数组,将方法名称输出到屏幕上。
对于Employee类的typeof运算,程序首先获取Employee类的Type实例mytype,然后获取mytype的字段数组,遍历该数组将所有字段的名称显示出来,最后获取mytype的方法数组,遍历该方法数组,将方法名称显示出来。
(2)本示例使用了System.Reflection命名空间中的MemberInfo类、MethodInfo类和FieldInfo类,需要在程序头部引入System.Reflection命名空间。
(3)GetMembers方法和GetMethods方法从Type类实例中获取成员变量和方法的时候,重载函数在数组中以多个元素的形式存在。在int数据类型成员变量和方法的输出结果中,可以看到多个ToString方法和Parse方法,原因是int数据类型中实现了这些方法的多种重载形式。
5.6 使用递增递减运算符
递增运算符和递减运算符的作用是将操作数增1或减1。递增操作符和递减操作符都具有两种形式。第一种形式,运算符位于操作数之前,称为前缀增(减)量操作,运算的结果是递增或递减之后的值。第二种形式,运算符位于操作符之后,称为后缀增(减)量操作,运算的结果是递增或递减之前的值。
技术要点
本示例主要说明了递增运算符和递减运算符的使用方法和注意事项,技术要点如下。
● 递增递减操作符直接在寄存器上实现变量的递增和递减,而不需要使用另外的寄存器保存操作符和操作数,在运行效率上高于+运算符(或-运算符)。常见于运算量比较大,对执行效率要求较高的各种循环中。
● 使用递增和递减运算符要特别注意运算的次序,在使用后缀运算时,操作数的值并不立即改变。
● 递增递减运算符常用于各种循环中,将迭代器作为操作数递增递减。在理解前缀运算符和后缀运算符的基础上,需要比较在循环时前缀和后缀操作的异同。前缀操作虽然能够立即改变操作数的值,但是因为for循环语句的迭代增量语句是在首次循环完毕以后才执行的,所以在for语句中的前缀操作和后缀操作原理不一致,但表现的结果一致。
实现步骤
(1)创建控制台应用程序项目,命名为“IncreaseAndDecreaseExample”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace IncreaseAndDecreaseExample { class Program { static void Main(string[] args) { int i = 6; //前缀增量运算时,立即改变i的值,此时显示的是已经增量的i值 Console.WriteLine("前缀增量操作的结果为:{0}",++i); i = 6; //后缀增量运算时,运算符不立即改变i的值,此时的i值仍然没有改变 Console.WriteLine("后缀增量操作的结果为:{0}",i++); //再调用i值的时候,i的增量结果才表现出来 Console.WriteLine("后缀增量操作后再调用时i值变为:{0}",i); i = 6; //前缀减量运算是,立即改变i的值,此时显示的是已经减量的i值 Console.WriteLine("前缀减量操作的结果为:{0}", --i); i = 6; //后缀减量时,运算符不立即改变i的值,此时的i值仍然没有改变 Console.WriteLine("后缀减量操作的结果为:{0}", i--); //再调用i值的时候,i的减量结果才表现出来 Console.WriteLine("后缀减量操作后再调用时i值变为:{0}", i); Console.Write("for循环的第一种递增:"); for (int j = 0; j < 5; ++j)//在for语句中前缀递增 { Console.Write(j + ","); } Console.WriteLine(""); Console.Write("for循环的第二种递增:"); for (int j = 0; j < 5; j++)//在for语句中后缀递增 { Console.Write(j + ","); } Console.WriteLine(""); Console.Write("for循环的第三种递增:"); for (int j = 0; j < 5;) { Console.Write(j++ + ",");//在循环中后缀递增 } Console.WriteLine(""); Console.Write("for循环的第四种递增:"); for (int j = 0; j < 5;) { Console.Write(++j + ",");//在循环中前缀递增 } Console.ReadLine(); } } }
(3)按F5键运行程序,运行结果如下所示。
前缀增量操作的结果为:7 后缀增量操作的结果为:6 后缀增量操作后再调用时i值变为:7 前缀减量操作的结果为:5 后缀减量操作的结果为:6 后缀减量操作后再调用时i值变为:5 for循环的第一种递增:0,1,2,3,4, for循环的第二种递增:0,1,2,3,4, for循环的第三种递增:0,1,2,3,4, for循环的第四种递增:1,2,3,4,5,
源程序解读
(1)本示例分别说明递增递减运算符的前缀操作和后缀操作。之后再通过递增运算符在for循环中的使用方法,说明在循环中使用递增递减运算符的注意事项。
(2)在本示例中,对于i的前缀递增,立即改变了i的值,在输出是值显示为7,之后的后缀递增,进行运算的时候,i的值并未递增,仍然显示为6,在经过++运算符操作以后,后续的程序调用i,递增的结果才真正体现,显示为7。这三个输出说明了前缀操作和后缀操作的差别。对于递减运算符,前缀和后缀操作的次序与递增是一样的。
(3)本示例还说明了递增递减出现最频繁的场合,即各种循环语句中应该注意的地方。程序使用具有代表性的for循环语句,通过四个示例程序的输出结果,说明了在for循环语句中递增前缀操作和后缀操作的不同表现。
5.7 使用赋值运算符
赋值运算符,即“=”运算符的作用,是将该运算符右边操作数的值存储在左边操作数的内存位置或者索引位置中。对于值类型数据类型,赋值操作是将右边操作数的值赋给左边操作数,两个操作数位于程序堆栈区的不同位置。对于引用类型数据类型,赋值操作是将右边操作数指向的托管内存位置赋给左边操作数,两个操作数指向托管内存中同一区域。在使用赋值运算的时候,运算符两边的操作数必须是相同或者兼容的数据类型,或者是支持隐式转换的数据类型。在使用赋值运算符的时候,数据类型的转换过程可能会包含装箱和取消装箱的操作。
技术要点
本示例主要说明了赋值运算符在实际使用中的引用类型数据类型的赋值和隐式转换赋值,技术要点如下。
● 在引用类型之间使用赋值运算符,将使两个引用类型的两个操作数指向托管内存中的同一位置,即当其中一个操作数改变时,另一个操作数也随即改变。对于属于引用类型的string操作数,在进行赋值运算的时候,两个操作数也是指向托管内存的同一位置,不同的是string类型的变量在修改的时候,将会在托管内存中创建新的内存区域,这将使两个string操作数在修改其中一个的时候,两个string操作数指向不同的托管内存位置。
● 赋值运算符要求两个操作数的数据类型一致时,或者支持右边操作数数据类型到左边操作数数据类型的隐式转换。
实现步骤
(1)创建控制台应用程序项目,命名为“AssignExample”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace AssignExample { class Program { static void Main(string[] args) { Employee empa = new Employee();//创建员工类的实例 empa.FirstName = "张"; empa.LastName = "三"; empa.Age = 34; Employee empb = empa;//引用类型的赋值 empb.Age = 35;//修改empb的属性,此操作将影响empa的值 Console.WriteLine("修改empb的年龄为35后empa的年龄属性为:{0}",empa.Age); int i = 6; double d = i;//赋值运算符两边的数据类型不一致,但支持隐式转换 Console.WriteLine("转换数据类型后的double变量值为:{0}",d); Console.ReadLine(); } class Employee//表示员工的类 { public string FirstName; public string LastName; public int Age; } } }
(3)按F5键运行程序,运行结果如下所示。
修改empb的年龄为35后empa的年龄属性为:35 转换数据类型后的double变量值为:6
源程序解读
(1)本示例程序的第一个输出说明在引用数据类型之间使用赋值运算符的时候,两个操作数指向托管内存中的同一位置。在将empa的值赋给empb的时候,实际是将empa所指向的内存位置赋给empb。当empb的属性Age修改时,empa的Age属性也发生变化。
(2)本示例程序的第二个输出,说明在赋值运算符两边的操作数数据类型不一致时发生隐式转换的情况。在本示例中,当int的值赋给double类型的变量时,程序执行了一次隐式转换。
5.8 使用关系运算符
关系运算符包含“==”、“!=”、“>”、“>=”、“<”、“<=”等运算符。这些运算符的主要作用是对两个操作数进行比较,返回一个boolean类型的结果。
技术要点
本示例主要说明了如何在程序中使用关系运算符,技术要点如下。
● 关系运算符都是可以重载的。在重载“==”运算符之后,必须同时重载“!=”运算符。在同时重载这两个运算符的时候,判断行为可以不一致,例如,可以在重载“==”运算符时,判断行为是比较两个实例的某个属性,如果属性一致就认为两个实例相等,在重载“!=”运算符的时候,判断行为可以定义为比较两个实例的所有属性,只有所有属性都一致时,才认为两个实例相等。
● 值类型的默认比较直接对值进行比较,引用类型的默认比较是判断两个操作数是否位于托管内存的同一位置。
● 引用类型的字符串,在使用“==”和“!=”运算符比较字符串的值,而不是和其他引用类型一样比较托管内存中的位置。
实现步骤
(1)创建控制台应用程序项目,命名为“CompareExample”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace CompareExample { class Program { static void Main(string[] args) { int inta = 6; int intb = 6; int intc = 5; //显示int数据类型的比较结果,代表值类型数据的比较 Console.WriteLine("int类型的比较结果:{0}-{1}", inta == intb,inta>=intc); Employee empa = new Employee("张", "三", 34); Employee empb = new Employee("张", "三", 34); Employee empc = empa; //显示Employee类的比较结果,代表引用类型数据的比较 Console.WriteLine("Employee类的比较结果:{0}-{1}", empa == empb,empc!=empa); string stra = "Test String"; string strb = "Test String"; string strc = stra; strc = "Test"; //对于引用类型的字符串,只比较字符串的值 Console.WriteLine("string类型的比较结果:{0}-{1}", stra == strb,strc!=stra); Department depta = new Department("工程部"); Department deptb = new Department("工程部"); //实现重载的运算符,Department类的比较只比较Name属性 Console.WriteLine("重载运算符后Department类的比较结果:{0}-{1}", depta==deptb, depta!=deptb); Console.ReadLine(); } class Employee//表示员工的类 { public string FirstName; public string LastName; int Age; public Employee(string firstname, string lastname, int age) { FirstName = firstname; LastName = lastname; Age = age; } } class Department//表示部门的类 { public string Name; public Department(string name) { Name = name; } //重载操作符“==”,在比较两个类实例时,只比较这两个实例的Name属性 public static bool operator ==(Department dept1,Department dept2) { return dept1.Name == dept2.Name; } //重载操作符“==”的时候,必须同时重载“!=”操作符,比较行为可以与“==”不一致 public static bool operator !=(Department dept1, Department dept2) { return dept1==dept2; } public override bool Equals(object obj)//完整的运算符重载需要重载的方法 { return base.Equals(obj); } public override int GetHashCode()//完整的运算符重载需要重载的方法 { return base.GetHashCode(); } } } }
(3)按F5键运行程序,运行结果如下所示。
int类型的比较结果:True-True Employee类的比较结果:False-False string类型的比较结果:True-True 重载运算符后Department类的比较结果:True-True
源程序解读
(1)本示例首先对值类型的数据类型使用关系运算符,在三个int数据类型的变量之间进行比较,然后对引用类型的数据类型使用关系运算符,在三个Employee类的实例之间进行比。接着,为了说明引用类型string数据类型的特殊性,在三个string类型的变量之间进行比较。最后,在两个重载了运算符“==”和“!=”的Department类实例之间进行比较。本示例程序的流程图如图5.4所示。
图5.4 关系运算符示例程序流程图
本示例通过各种比较的示例和显示结果充分说明了关系运算符的使用方法。
(2)本示例的第一个比较,在三个值类型的数据之间进行,只要是值类型,包括结构,两个操作数的数据一致时,即认为两个数据是相等的。第二个比较在三个类实例之间进行,只有当两个类实例指向同一个托管内存位置时,才判断这两个实例是相等的。第三个比较在三个字符串之间进行,说明了string作为引用类型在使用关系运算符时的特殊性。第四个比较在两个Department类实例之间进行,Department类中实现了运算符的重载,在该比较中分别说明了关系运算符的重载以及重载后对运算符判断行为的改变。
(3)重载“==”运算符的时候,必须同时重载“!=”运算符,否则会引起编译错误。此外,还应该在类中实现Equals方法和GetHashCode方法的重载,否则将会引起编译警告。
(4)为了说明问题,本示例特地在重载Department类的“==”运算符和“!=”运算符时,定义了不同的判断行为,从显示的结果可以看到,这种定义是可行的、有效的。
5.9 使用逻辑运算符
C#编程语言共有9个逻辑运算符,分别是“&”与运算符、“|”或运算符、“^”异或运算符、“!”非运算符、“~”按位求补运算符、“&&”逻辑与运算符、“||”逻辑或运算符、用于重载的“true”运算符和“false”运算符。
技术要点
本示例主要说明了各种逻辑运算符的使用方法,技术要点如下。
● &运算符在不安全代码中作为一元运算符时,表示获取操作数的内存地址。作为二元运算符时,表示两个操作数的按位与运算。
● 重载true运算符时必须同时重载false运算符,即需要成对重载。true和false的重载并不能直接体现,只有在使用其他运算符,例如&运算符和|运算符等,才能体现出来。
● 逻辑运算符“&&”和“||”都存在“短路”计算的现象。对于“&&”运算符,当第一个操作数为false时,不再计算第二个操作数,对于“||”运算符,当第一个操作数为false时,不再计算第二个操作数。
实现步骤
(1)创建控制台应用程序项目,命名为“LogicExample”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace LogicExample { class Program { static void Main(string[] args) { //将两个操作数作二进制的“与”运算,即只有两个操作数的当前位数同时为1时,运算结果才为1 Console.WriteLine("0xFF按位与0xF0的结果是:{0:X}", 0xFF & 0xF0); //将两个操作数作二进制的“或”运算,即只要两个操作数的当前位数其中一个为1时,运算结果就是1 Console.WriteLine("0xF0按位或0x0F的结果是:{0:X}", 0xF0 | 0x0F); //将两个操作数作二进制的“异或”运算,即只要两个操作数二进制取反后,再进行或运算 Console.WriteLine("0x0F按位异或0xFF的结果是:{0:X}", 0x0F ^ 0xFF); //将一个操作数作“非”运算,即当前操作数取反 Console.WriteLine("true非运算的结果是:{0}", !true); //将一个操作数作按位求补运算,即当前操作数与运算结果相加,得到当前数据类型的最大取值 Console.WriteLine("0xF0按位求补的结果是:{0:X}", ~0xF0); //逻辑与运算,对应与两个操作数的“与”运算 Console.WriteLine("true逻辑与false的结果是:{0}", true&&false); //逻辑或运算,对应与两个操作数的“或”运算 Console.WriteLine("true逻辑或false的结果是:{0}", true || false); //以下说明true和false运算符重载的使用方法,这两个运算符的重载需要配合其他运算符的重载 MyStruct strcta = new MyStruct("Name1",true); MyStruct strctb = new MyStruct("Name2",false); //在两个MyStruct结构实例之间进行逻辑与运算 Console.WriteLine("strcta逻辑与strctb的结果是:{0:X}", strcta && strctb); //在进行逻辑与运算时,产生的“短路”计算 Console.WriteLine("strctb逻辑与strcta的结果是:{0:X}", strctb && strcta); Console.ReadLine(); } struct MyStruct//自定义的结构 { public string Name; public bool Value; //结构的构造函数,根据传入的参数为字段赋值 public MyStruct(string name, bool value) { Name = name; Value = value; } //重载&运算符,当两个MyStruct结构实例进行“与”运算时执行的处理代码。 public static MyStruct operator &(MyStruct Key1, MyStruct Key2) { //将两个操作数的Name字段相加,Value字段进行“与”运算 string newname=Key1.Name+Key2.Name; bool newvalue = !Key1.Value ? Key1.Value : Key1.Value & Key2.Value; return new MyStruct(newname,newvalue); } //重载true运算符,将Value字段的值作为MyStruct逻辑运算时的取值 public static bool operator true(MyStruct mystruct) { return mystruct.Value==true; } //重载false运算符,将Value字段的值作为MyStruct逻辑运算时的取值 public static bool operator false(MyStruct mystruct) { return mystruct.Value==false; } //重载ToString方法,将表示Name字段和Value字段的字符串显示出来 public override string ToString() { return Name+":"+Value.ToString(); } } } }
(3)按F5键运行程序,运行结果如下所示。
0xFF按位与0xF0的结果是:F0 0xF0按位或0x0F的结果是:FF 0x0F按位异或0xFF的结果是:F0 true非运算的结果是:False 0xF0按位求补的结果是:FFFFFF0F true逻辑与false的结果是:False true逻辑或false的结果是:True strcta逻辑与strctb的结果是:Name1Name2:Fals strctb逻辑与strcta的结果是:Name2:False
源程序解读
(1)本示例分别说明了各种逻辑运算符的使用方法,在自定义的结构中实现了运算符的重载,并特别指出了逻辑运算中的“短路”计算。本示例程序的流程图如图5.5所示。
图5.5 逻辑运算符示例程序流程图
(2)在本示例中的自定义结构MyStruc,只是为了说明true和false运算符的用法,在结构中简单地重载了&运算符。为了便于显示,还重载了MyStruc的ToString方法。
(3)在自定义结构MyStruct中,重载的&运算符将两个操作数的Name字段相加,并对Value字段作“与”运算。在strcta结构实例和strctb结构实例进行逻辑与运算的时候,因为strcta结构实例的Value字段值是true,所以计算会继续下去。而在strctb结构实例和strcta结构实例进行逻辑与运算的时候,因为strctb结构实例的Value字段值是false,所以strcta的值将不再计算,这种现象就是逻辑运算中的“短路”计算。
5.10 使用算术运算符
C#编程语言中包含5个算术运算符,分别是“+”运算符、“-”运算符、“*”运算符、“/”运算符、“%”运算符。其中“+”运算符、“-”运算符、“*”运算符可作为一元运算符使用。“+”运算符作为一元运算符使用时,运算结果就是操作数的值。“-”运算符作为一元运算符使用时,运算结果时操作数的反数。而“*”运算符作为一元运算符时,只能用在不安全代码中,用来声明指针或者取消引用指针。当“+”运算符、“-”运算符和“*”运算符作为一元运算符使用时,并不作算术运算,只有作为二元运算符时,才作为算术运算符。
技术要点
本示例主要说明了算术运算符的使用方法,技术要点如下。
● 在C#中的“/”运算和“%”运算,对操作数有限制,其中“/”运算中除数不能为0,而“%”运算只能在整型的数据类型之间进行。
● 算术运算符都是可以重载的,在重载算术运算符的时候,隐性地重载了“=”运算符。
● 定义或重载运算符将可以在各种对象之间进行直观方便的运算。常见于各种需要计算的场合。例如复数的计算,可以创建一个复数类,按照复数的运算规则重载各种算术运算符,就能实现复数的计算。
● 定义或重载运算符时,必须加入“public”和“statuc”的前缀。
实现步骤
(1)创建控制台应用程序项目,命名为“MathExample”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace MathExample { class Program { static void Main(string[] args) { Console.WriteLine("+运算的实例结果:{0}", 1.3 + 4.8); Console.WriteLine("-运算的实例结果:{0}", 1.3 - 4.8); Console.WriteLine("*运算的实例结果:{0}", 1.3 * 4.8); Console.WriteLine("/运算的实例结果:{0}", 1.3 / 4.8); //取余数运算的操作数必须为整型的数据类型 Console.WriteLine("%运算的实例结果:{0}", 8 % 3); MyAccount accounta = new MyAccount("AccountA", 232.31d); MyAccount accountb = new MyAccount("AccountB", 195.74d); MyAccount sum = accounta + accountb; Console.WriteLine("重载+运算符实例结果:{0}——{1}", sum.Name,sum.Value); Console.ReadLine(); } struct MyAccount//自定义的账目结构 { public string Name; public double Value; //账目结构的构造函数,将传入的参数值赋给结构中的字段 public MyAccount(string name, double value) { Name = name; Value = value; } //重载“+”运算符,两个参数表示两个操作数,即当前重载的是“+”的二元操作符 public static MyAccount operator +(MyAccount account1, MyAccount account2) { MyAccount result = new MyAccount("总计", account1.Value + account2.Value); return result; } } } }
(3)按F5键运行程序,运行结果如下所示。
+运算的实例结果:6.1 -运算的实例结果:-3.5 *运算的实例结果:6.24 /运算的实例结果:0.270833333333333 %运算的实例结果:2 重载+运算符实例结果:总计——428.05
源程序解读
(1)本示例在两个操作数之间分别使用不同的算术运算符,并输出结果到屏幕上,应注意在进行“/”运算和“%”运算时,操作数的取值规定。
(2)本示例还定义了一个名为MyAccount的结构,在该结构中定义“+”操作符,即定义在两个MyAccount结构实例相加时的行为,即将运算结果的Name字段改为“总计”,然后将两个操作数的Value字段相加,作为运算结果的Value字段。
(3)程序创建两个MyAccount结构的实例accounta和accountb,对两个实例进行“+”运算,将运算结果保存到sum的MyAccount结构实例中,并将相加的结果显示出来。