C#高级编程.第6章运算符和类型强制转换.doc

上传人:飞****2 文档编号:52748844 上传时间:2022-10-23 格式:DOC 页数:30 大小:199.50KB
返回 下载 相关 举报
C#高级编程.第6章运算符和类型强制转换.doc_第1页
第1页 / 共30页
C#高级编程.第6章运算符和类型强制转换.doc_第2页
第2页 / 共30页
点击查看更多>>
资源描述

《C#高级编程.第6章运算符和类型强制转换.doc》由会员分享,可在线阅读,更多相关《C#高级编程.第6章运算符和类型强制转换.doc(30页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。

1、第6章 运算符和类型强制转换前几章介绍了使用C#编写程序所需要的大部分知识。本章将首先讨论基本语言元素,接着论述C#语言的扩展功能。本章的主要内容如下:C#中的可用运算符处理引用类型和值类型时相等的含义基本数据类型之间的数据转换使用装箱技术把值类型转换为引用类型通过强制转换技术在引用类型之间转换重载标准的运算符,以支持对定制类型的操作给定制类型添加强制转换运算符,以支持无缝的数据类型转换6.1 运算符C和C+开发人员应很熟悉大多数C#运算符,这里为新程序员和Visual Basic开发人员介绍最重要的运算符,并介绍C#中的一些新变化。C#支持表6-1所示的运算符。表 6-1类 别运 算 符算术

2、运算符+ * / %逻辑运算符& | & | !字符串连接运算符+增量和减量运算符+ 移位运算符比较运算符= != =赋值运算符= += = *= /= %= &= |= = =成员访问运算符(用于对象和结构).索引运算符(用于数组和索引器)数据类型转换运算符()条件运算符 (三元运算符)?:委托连接和删除运算符(见第7章)+ 对象创建运算符new类型信息运算符sizeof (只用于不安全的代码) is typeof as溢出异常控制运算符checked unchecked间接寻址运算符* & (只用于不安全代码) 命名空间别名限定符(见第2章):空接合运算符?有4个运算符(sizeof、*、

3、-、&)只能用于不安全的代码(这些代码绕过了C#类型安全性的检查),这些不安全的代码见第12章的讨论。还要注意,sizeof运算符在.NET Framework 1.0和1.1中使用,它需要不安全模式。自从.NET Framework 2.0以来,就没有这个运算符了。类 别运 算 符运算符关键字sizeof(仅用于.NET Framework 1.0和1.1)运算符*、&使用C#运算符的一个最大缺点是,与C风格的语言一样,赋值(=)和比较(=)运算使用不同的运算符。例如,下述语句表示x等于3:x = 3;如果要比较x和另一个值,就需要使用两个等号(=):if (x = 3)C#非常严格的类型安

4、全规则防止出现常见的C#错误,也就是在逻辑语句中使用赋值运算符代替比较运算符。在C#中,下述语句会产生一个编译错误:if (x = 3)习惯使用宏字符&来连接字符串的Visual Basic程序员必须改变这个习惯。在C#中,使用加号+连接字符串,而&表示两个不同整数值的按位AND运算。| 则在两个整数之间执行按位OR运算。Visual Basic程序员可能还没有使用过(取模)运算符,它返回除运算的余数,例如,如果x等于7,则x % 5会返回2。在C#中很少会用到指针,因此也很少用到间接寻址运算符(-)。使用它们的唯一场合是在不安全的代码块中,因为只有在此C#才允许使用指针。指针和不安全的代码见

5、第12章。6.1.1 运算符的简化操作表6-2列出了C#中的全部简化赋值运算符。表 6-2运算符的简化操作等 价 于x+, +xx = x + 1x , xx = x 1x+= yx = x + yx= yx = x yx *= yx = x * yx /= yx = x / yx %= yx = x % yx = yx = x yx = yx = x = b)Console.WriteLine(a = b);elseConsole.WriteLine(a b); 注意:null值的可能性表示,不能随意合并表达式中的可空类型和非可空类型,详见本章后面的内容。6.1.9 空接合运算符空接合运算符

6、(?)提供了一种快捷方式,可以在处理可空类型和引用类型时表示null值。这个运算符放在两个操作数之间,第一个操作数必须是一个可空类型或引用类型,第二个操作数必须与第一个操作数的类型相同,或者可以隐含地转换为第一个操作数的类型。空接合运算符的计算如下:如果第一个操作数不是null,则整个表达式就等于第一个操作数的值。但如果第一个操作数是null,则整个表达式就等于第二个操作数的值。例如:int? a = null;int b;b = a ? 10; / b has the value 10a = 3;b = a ? 10; / b has the value 3 如果第二个操作数不能隐含地转换为

7、第一个操作数的类型,就生成一个编译错误。6.1.10 运算符的优先级表6-3显示了C#运算符的优先级。表顶部的运算符有最高的优先级(即在包含多个运算符的表达式中,最先计算该运算符):表 6-3组运 算 符初级运算符() . x+ x new typeof sizeof checked unchecked一元运算符+ ! +x x和数据类型转换乘/除运算符* / %加/减运算符+ 移位运算符关系运算符 = is as比较运算符= = !=按位AND运算符&按位XOR运算符按位OR运算符|布尔 AND运算符&布尔OR运算符|条件运算符?:赋值运算符= += = *= /= %= &= |= = =

8、 =注意:在复杂的表达式中,应避免利用运算符优先级来生成正确的结果。使用括号指定运算符的执行顺序,可以使代码更整洁,避免出现潜在的冲突。6.2 类型的安全性第1章提到中间语言(IL)可以对其代码强制加上强类型安全性。强类型支持.NET提供的许多服务,包括安全性和语言的交互性。因为C#这种语言会编译为IL,所以C#也是强类型的。这说明数据类型并不总是可互换的。本节将介绍基本类型之间的转换。注意:C#还支持在不同引用类型之间的转换,允许指定自己创建的数据类型如何与其他类型进行相互转换。这些论题将在本章后面讨论。泛型是C#中的一个特性,它可以避免对一些常见的情形进行类型转换,泛型详见第9章。6.2.

9、1 类型转换我们常常需要把数据从一种类型转换为另一种类型。考虑下面的代码:byte value1 = 10;byte value2 = 23;byte total;total = value1 + value2;Console.WriteLine(total);在编译这些代码时,会产生一个错误:Cannot implicitly convert type int to byte (不能把int类型隐式地转换为byte类型)。问题是,我们把两个byte型数据加在一起时,应返回int型结果,而不是另一个byte。这是因为byte包含的数据只能为8位,所以把两个byte型数据加在一起,很容易得到不能

10、存储在byte变量中的值。如果要把结果存储在一个byte变量中,就必须把它转换回byte。C#有两种转换方式:隐式转换方式和显式转换方式。1. 隐式转换方式只要能保证值不会发生任何变化,类型转换就可以自动进行。这就是前面代码失败的原因:试图从int转换为byte,而潜在地丢失了3个字节的数据。编译器不会告诉我们该怎么做,除非我们明确告诉它这就是我们希望的!如果在long型变量中存储结果,而不是byte型变量中,就不会有问题了:byte value1 = 10;byte value2 = 23;long total; / this will compile finetotal = value1

11、+ value2;Console.WriteLine(total);这是因为long类型变量包含的数据字节比byte类型多,所以数据没有丢失的危险。在这些情况下,编译器会很顺利地转换,我们也不需要显式提出要求。表6-4介绍了C#支持的隐式类型转换。表 6-4源 类 型目 的 类 型sbyteshort、int、long、float、double、decimalbyteshort、ushort、int、uint、long、ulong、float、double、decimalshortint、long、float、double、decimalushortint、uint、long、ulong、fl

12、oat、double、decimalintlong、float、double、decimaluintlong、ulong、float、double、decimallong、ulongfloat、double、decimalfloatdoublecharushort、int、uint、long、ulong、float、double、decimal注意,只能从较小的整数类型隐式地转换为较大的整数类型,不能从较大的整数类型隐式地转换为较小的整数类型。也可以在整数和浮点数之间转换,其规则略有不同,可以在相同大小的类型之间转换,例如int/uint转换为 float,long/ulong转换为doubl

13、e,也可以从long/ulong转换回float。这样做可能会丢失4个字节的数据,但这仅表示得到的float值比使用double得到的值精度低,编译器认为这是一种可以接受的错误,而其值的大小是不会受到影响的。无符号的变量可以转换为有符号的变量,只要无符号的变量值的大小在有符号的变量的范围之内即可。在隐式转换值类型时,可空类型需要额外考虑:可空类型隐式转换为其他可空类型,应遵循表6-4中非可空类型的转换规则。即int? 隐式转换为long?、float?、double?和decimal?。非可空类型隐式转换为可空类型也遵循表6-4中的转换规则,即int隐式转换为long?、float?、doub

14、le?和decimal?。可空类型不能隐式转换为非可空类型,此时必须进行显式转换,如下一节所述。这是因为可空类型的值可以是null,但非可空类型不能表示这个值。2. 显式转换方式有许多场合不能隐式地转换类型,否则编译器会报告错误。下面是不能进行隐式转换的一些场合:int转换为short- 会丢失数据int转换为uint- 会丢失数据uint转换为int- 会丢失数据float转换为int- 会丢失小数点后面的所有数据任何数字类型转换为char - 会丢失数据decimal转换为任何数字类型- 因为decimal 类型的内部结构不同于整数和浮点数int? 转换为int- 可空类型的值可以是nul

15、l 但是,可以使用cast显式执行这些转换。在把一种类型强制转换为另一种类型时,要迫使编译器进行转换。类型转换的一般语法如下:long val = 30000;int i = (int)val; / A valid cast. The maximum int is 这表示,把转换的目标类型名放在要转换的值之前的圆括号中。对于熟悉C的程序员来说,这是数据类型转换的典型语法。对于熟悉C+数据类型转换关键字(如static_cast)的程序员来说,这些关键字在C#中不存在,必须使用C风格的旧语法。这种类型转换是一种比较危险的操作,即使在从long转换为int这样简单的转换过程中,如果原来long的值

16、比int的最大值还大,就会出问题:long val = ;int i = (int)val; / An invalid cast. The maximum int is 在本例中,不会报告错误,也得不到期望的结果。如果运行上面的代码,结果存储在i中,则其值为:-最好假定显式数据转换不会给出希望的结果。如前所述,C#提供了一个checked运算符,使用它可以测试操作是否会产生算术溢出。使用这个运算符可以检查数据类型转换是否安全,如果不安全,就会让运行库抛出一个溢出异常:long val = ;int i = checked (int)val);记住,所有的显式数据类型转换都可能不安全,在应用程序

17、中应包含这样的代码,处理可能失败的数据类型转换。第14章将使用try和 catch语句引入结构化异常处理。使用数据类型转换可以把大多数数据从一种基本类型转换为另一种基本类型。例如:给price加上0.5,再把结果转换为int:double price = 25.30;int approximatePrice = (int)(price + 0.5);这么做的代价是把价格四舍五入为最接近的美元数。但在这个转换过程中,小数点后面的所有数据都会丢失。因此,如果要使用这个修改过的价格进行更多的计算,最好不要使用这种转换;如果要输出全部计算完或部分计算完的近似值,且不希望用小数点后面的数据去麻烦用户,这

18、种转换是很好的。下面的例子说明了把一个无符号的整数转换为char型时,会发生的情况:ushort c = 43;char symbol = (char)c;Console.WriteLine(symbol);结果是ASCII编码为43的字符,即+号。可以尝试数字类型之间的任何转换(包括char),这种转换是成功的,例如把decimal转换为char,或把char转换为decimal。值类型之间的转换并不仅限于孤立的变量。还可以把类型为double的数组元素转换为类型为int的结构成员变量:struct ItemDetails public string Description;public i

19、nt ApproxPrice;/.double Prices = 25.30, 26.20, 27.40, 30.00 ;ItemDetails id;id.Description = Whatever;id.ApproxPrice = (int)(Prices0 + 0.5);要把一个可空类型转换为非可空类型,或转换为另一个可空类型,但可能会丢失数据,就必须使用显式转换。重要的是,在底层基本类型相同的元素之间进行转换时,就一定要使用显式转换,例如int?转换为int,或float?转换为float。这是因为可空类型的值可以是null,非可空类型不能表示这个值。只要可以在两个非可空类型之间进行

20、显式转换,对应可空类型之间的显式转换就可以进行。但如果从可空类型转换为非可空类型,且变量的值是null,就会抛出InvalidOperationException。例如:int? a = null;int b = (int)a; / Will throw exception使用显式的数据类型转换方式,并小心使用这种技术,就可以把简单值类型的任何实例转换为另一种类型。但在进行显式的类型转换时有一些限制,例如值类型,只能在数字、char类型和enum类型之间转换。不能直接把Boolean数据类型转换为其他类型,也不能把其他类型转换为Boolean数据类型。如果需要在数字和字符串之间转换,.NET类

21、库提供了一些方法。Object类有一个ToString()方法,该方法在所有的.NET预定义类型中都进行了重写,返回对象的字符串表示:int i = 10;string s = i.ToString();同样,如果需要分析一个字符串,获得一个数字或Boolean值,就可以使用所有预定义值类型都支持的Parse方法:string s = 100;int i = int.Parse(s);Console.WriteLine(i + 50); / Add 50 to prove it is really an int注意,如果不能转换字符串(例如要把字符串Hello转换为一个整数),Parse方法就

22、会注册一个错误,抛出一个异常。第14章将介绍异常。6.2.2 装箱和拆箱第2章介绍了所有类型,包括简单的预定义类型,例如int和char,以及复杂类型,例如从Object类型中派生的类和结构。下面可以像处理对象那样处理字面值:string s = 10.ToString();但是,C#数据类型可以分为在堆栈上分配内存的值类型和在堆上分配内存的引用类型。如果int不过是堆栈上一个4字节的值,该如何在它上面调用方法?C#的实现方式是通过一个有点魔术性的方式,即装箱(boxing)。装箱和拆箱(unboxing)可以把值类型转换为引用类型,或把引用类型转换为值类型。这已经在数据类型转换一节中介绍过了

23、,即把值转换为object类型。装箱用于描述把一个值类型转换为引用类型。运行库会为堆上的对象创建一个临时的引用类型box。该转换是隐式进行的,如上面的例子所述。还可以进行显式转换:int myIntNumber = 20;object myObject = myIntNumber;拆箱用于描述相反的过程,即以前装箱的值类型转换回值类型。这里使用术语cast,是因为这种数据类型转换是显式进行的。其语法类似于前面的显式类型转换:int myIntNumber = 20;object myObject = myIntNumber; / Box the intint mySecondNumber =

24、(int)myobject; / Unbox it back into an int只能把以前装箱的变量再转换为值类型。当o不是装箱后的int型时,如果执行上面的代码,就会在运行期间抛出一个异常。这里有一个警告。在拆箱时,必须非常小心,确保得到的值变量有足够的空间存储拆箱的值中的所有字节。例如,C#的int只有32位,所以把long值(64位)拆箱为int时,会产生一个InvalidCastException异常:long myLongNumber = ;object myObject = (object)myLongNumber;int myIntNumber = (int)myObject

25、;6.3 对象的相等比较在讨论了运算符,并简要介绍了相等运算符后,就应考虑在处理类和结构的实例时,相等意味着什么。理解对象相等比较的机制对编写逻辑表达式非常重要,另外,对实现运算符重载和数据类型转换也非常重要,本章的后面将讨论运算符重载。对象相等比较的机制对于引用类型(类的实例)的比较和值类型(基本数据类型,结构或枚举的实例)的比较来说是不同的。下面分别介绍引用类型和值类型的相等比较。6.3.1 引用类型的相等比较System.Object定义了3个不同的方法,来比较对象的相等性:ReferenceEquals()和Equals()的两个版本。再加上比较运算符=,实际上有4种进行相等比较的方式

26、。这些方法有一些微妙的区别,下面就介绍这些方法。1. ReferenceEquals()方法ReferenceEquals()是一个静态方法,测试两个引用是否指向类的同一个实例,即两个引用是否包含内存中的相同地址。作为静态方法,它不能重写,所以只能使用System.Object的实现代码。如果提供的两个引用指向同一个对象实例,ReferenceEquals()总是返回true,否则就返回false。但是它认为null等于null:SomeClass x, y;x = new SomeClass();y = new SomeClass();bool B1 = ReferenceEquals(nu

27、ll, null); /return truebool B2 = ReferenceEquals(null, x); /return falsebool B3 = ReferenceEquals(x, y); /return false because x and y /point to different objects2. 虚拟的Equals()方法Equals()虚拟版本的System.Object实现代码也可以比较引用。但因为这个方法是虚拟的,所以可以在自己的类中重写它,按值来比较对象。特别是如果希望类的实例用作字典中的键,就需要重写这个方法,以比较值。否则,根据重写Object.Ge

28、tHashCode()的方式,包含对象的字典类要么不工作,要么工作的效率非常低。在重写Equals()方法时要注意,重写的代码不会抛出异常。这是因为如果抛出异常,字典类就会出问题,一些在内部调用这个方法的.NET基类也可能出问题。3. 静态的Equals()方法Equals()的静态版本与其虚拟实例版本的作用相同,其区别是静态版本带有两个参数,并对它们进行相等比较。这个方法可以处理两个对象中有一个是null的情况,因此,如果一个对象可能是null,这个方法就可以抛出异常,提供额外的保护。静态重载版本首先要检查它传送的引用是否为null。如果它们都是null,就返回true(因为null与nul

29、l相等)。如果只有一个引用是null,就返回false。如果两个引用都指向某个对象,它就调用Equals()的虚拟实例版本。这表示在重写Equals()的实例版本时,其效果相当于也重写了静态版本。4. 比较运算符最好将比较运算符看作是严格的值比较和严格的引用比较之间的中间选项。在大多数情况下,下面的代码表示比较引用:bool b = (x = y); /x, y object references但是,如果把一些类看作值,其含义就会比较直观。在这些情况下,最好重写比较运算符,以执行值的比较。后面将讨论运算符的重载,但显然它的一个例子是System.String类,Microsoft重写了这个运

30、算符,比较字符串的内容,而不是它们的引用。6.3.2 值类型的相等比较在进行值类型的相等比较时,采用与引用类型相同的规则:ReferenceEquals()用于比较引用,Equals()用于比较值,比较运算符可以看作是一个中间项。但最大的区别是值类型需要装箱,才能把它们转换为引用,才能对它们执行方法。另外,Microsoft已经在System.ValueType类中重载了实例方法Equals(),以便对值类型进行合适的相等测试。如果调用sA.Equals(sB),其中sA和sB是某个结构的实例,则根据sA和sB是否在其所有的字段中包含相同的值,而返回true或false。另一方面,在默认情况下

31、,不能对自己的结构重载=运算符。在表达式中使用(sA=sB)会导致一个编译错误,除非在代码中为结构提供了=的重载版本。另外,ReferenceEquals()在应用于值类型时,总是返回false,因为为了调用这个方法,值类型需要装箱到对象中。即使使用下面的代码:bool b = ReferenceEquals(v, v); /v is a variable of some value type也会返回false,因为在转换每个参数时,v都会被单独装箱,这意味着会得到不同的引用。调用ReferenceEquals()来比较值类型实际上没有什么意义。尽管System.ValueType提供的Equ

32、als()的默认重写代码肯定足以应付绝大多数自定义的结构,但仍可以为自己的结构重写它,以提高性能。另外,如果值类型包含作为字段的引用类型,就需要重写Equals(),以便为这些字段提供合适的语义,因为Equals()的默认重写版本仅比较它们的地址。6.4 运算符重载本节将介绍为类或结构定义的另一种类型的成员:运算符重载。C+开发人员应很熟悉运算符重载。但是,因为这个概念对Java和Visual Basic开发人员来说是全新的,所以这里要解释一下。C+开发人员可以直接跳到主要示例上。运算符重载的关键是在类实例上不能总是调用方法或属性,有时还需要做一些其他的工作,例如对数值进行相加、相乘或逻辑操作

33、,如比较对象等。假定要定义一个类,表示一个数学矩阵,在数学中,矩阵可以相加和相乘,就像数字一样。所以可以编写下面的代码:Matrix a, b, c;/ assume a, b and c have been initialized Matrix d = c * (a + b);通过重载运算符,就可以告诉编译器,+和*对Matrix对象进行什么操作,以编写上面的代码。如果用不支持运算符重载的语言编写代码,就必须定义一个方法,以执行这些操作,结果肯定不太直观,如下所示。Matrix d = c.Multiply(a.Add(b);学习到现在,像+和*这样的运算符只能用于预定义的数据类型,原因很简

34、单:编译器认为所有常见的运算符都是用于这些数据类型的,例如,它知道如何把两个long加起来,或者如何从一个double中减去另一个double,并生成合适的中间语言代码。但在定义自己的类或结构时,必须告诉编译器:什么方法可以调用,每个实例存储了什么字段等所有的信息。同样,如果要在自己的类上使用运算符,就必须告诉编译器相关的运算符在这个类中的含义。此时就要定义运算符重载。要强调的另一个问题是重载不仅仅限于算术运算符。还需要考虑比较运算符 =、!=、=和=。例如,语句if(a=b)。对于类,这个语句在默认状态下会比较引用a和b,检测这两个引用是否指向内存中的同一个地址,而不是检测两个实例是否包含相

35、同的数据。对于string类,这种操作就会重写,比较字符串实际上就是比较每个字符串的内容。可以对自己的类进行这样的操作。对于结构,=运算符在默认状态下不做任何工作。试图比较两个结构,看看它们是否相等,就会产生一个编译错误,除非显式重载了=,告诉编译器如何进行比较。在许多情况下,重载运算符允许生成可读性更高、更直观的代码,包括:在数学领域中,几乎包括所有的数学对象:坐标、矢量、矩阵、张量和函数等。如果编写一个程序执行某些数学或物理建模,肯定会用类表示这些对象。图形程序在计算屏幕上的位置时,也使用数学或相关的坐标对象。表示大量金钱的类(例如,在财务程序中)。字处理或文本分析程序也有表示语句、子句等的类,可以使用运算符把语句连接在一起(这是字符串连接的一种比较复杂的版本)。另外,有许多类与运算符重

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育专区 > 教案示例

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号© 2020-2023 www.taowenge.com 淘文阁