第5章 运算符和类型强制转换前几章介绍了使用 C#编写程序所需要的大部分知识。本章将首先讨论基本语言元素,接着论述 C#语言的扩展功能。本章的主要内容如下:● C#中的可用运算符● 处理引用类型和值类型时相等的含义● 基本数据类型之间的数据转换● 使用装箱技术把值类型转换为引用类型● 通过强制转换技术在引用类型之间转换● 重载标准的运算符,以支持对定制类型的操作● 给定制类型添加强制转换运算符,以支持无缝的数据类型转换5.1 运算符C 和 C++开发人员应很熟悉大多数 C#运算符,这里为新程序员和 VB 开发人员介绍最重要的运算符,并介绍 C#中的一些新变化。C#支持表 5-1 所示的运算符,其中有 4 个运算符(sizeof、 *、–> 、&)只能用于不安全的代码(这些代码绕过了 C#类型安全性的检查),这些不安全的代码见第 7 章的讨论。表 5-1类 别 运 算 符算术运算符 + – * / %逻辑运算符 & | ^ ~ && || !字符串连接运算符 +增量和减量运算符 ++ – –移位运算符 >比较运算符 == != =赋值运算符 = += –= *= /= %= &= |= ^= >=成员访问运算符(用于对象和结构 ) .索引运算符(用于数组和索引器) []数据类型转换运算符 ()条件运算符 (三元运算符) ?:对象创建运算符 new类型信息运算符 sizeof (只用于不安全的代码) is typeof as溢出异常控制运算符 checked unchecked间接寻址运算符 * –> & (只用于不安全代码) []C#高级编程(第 3 版)• 116 •使用 C#运算符的一个最大缺点是,与 C 风格的语言一样,赋值 (=)和比较(==)运算使用不同的运算符。下述语句表示“x 等于 3”:x = 3;如果要比较 x 和另一个值,就需要使用两个等号(==):if (x == 3)C#非常严格的类型安全规则防止出现常见的 C#错误,也就是在逻辑语句中使用赋值运算符代替比较运算符。在 C#中,下述语句会产生一个编译错误:if (x = 3)习惯使用宏字符&来连接字符串的 VB 程序员必须改变这个习惯。在 C#中,使用加号+连接字符串,而&表示两个不同整数值的按位 AND 运算。| 则在两个整数之间执行按位 OR 运算。VB 程序员可能还没有使用过%( 取模) 运算符,它返回除运算的余数,例如,如果 x 等于 7,则x% 5 会返回 2。在 C#中很少会用到指针,因此也很少会用到间接寻址运算符(–>)。使用它们的惟一场合是在不安全代码块中,因为只有在此 C#才允许使用指针。5.1.1 运算符的简化 操作表 5-2 列出了 C#中的全部简化赋值运算符。表 5-2运算符的简化操作 等 价 于x++, ++x x = x + 1x–,–x x = x – 1x+= y x = x + yx–= y x = x – yx *= y x = x * yx /= y x = x / yx %= y x = x % yx >>= y x = x >> yx >关系运算符 = is as比较运算符 = = !=按位 AND 运算符 &按位 XOR 运算符 |按位 OR 运算符 ^布尔 AND 运算符 &&布尔 OR 运算符 ||三元运算符 ?:赋值运算符 = += –= *= /= %= &= |= ^= >= >>>=注意:在复杂的表达式中,应避免利用运算符 优先级来生成正确的 结果。使用括号指定运算符的 执行顺序,可以使代码更整洁,避免出现潜在的冲突。5.2 类型的安全性第 1 章提到中间语言(IL)可以对其代码强制加上强类型安全性。强类型支持.NET 提供的许多服务,包括安全性和语言的交互性。因为 C#这种语言将编译为 IL,所以 C#也是强类型的。这说明数据类型并不总是可互换的。本节将介绍基本类型之间的转换。第 5 章 运算符和类型强制转换 • 121 •注意:C#还支持在不同引用类型之间 的转换,允 许指定自己创建的数据类型如何与其他类型进行相互转换。这些论题将在本章后面 讨论。5.2.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 加在一起很容易得到不能存储在一个 byte 中的值。如果要把结果存储在一个 byte 变量中,就必须把它转换回 byte。有两种转换方式,隐式转换方式和显式转换方式。1. 隐式转换方式如果能保证值不会发生任何变化,类型转换就可以自动进行。这就是前面代码失败的原因:试图从 int 转换为 byte,而潜在地丢失了 3 个字节的数据。编译器不会告诉我们该怎么做,除非我们显式地告诉它这就是我们希望的!如果在 long 型变量中存储结果,而不是 byte 型变量中,就不会有问题了:byte value1 = 10;byte value2 = 23;long total; // this will compile finetotal = value1 + value2;Console.WriteLine(total);这是因为 long 型变量包含的数据字节比 int 型多,所以数据没有丢失的危险。在这些情况下,编译器会很顺利地进行转换,我们也不需要显式提出要求。表 5-4 介绍了 C#支持的隐式类型转换。表 5-4源 类 型 目 的 类 型sbyte short、int、long、float、double 、decimalbyte short、ushort、int、uint、long、ulong、float、double 、decimalshort int、long、float、double 、decimalushort int、uint、long、ulong、float 、double、decimalC#高级编程(第 3 版)• 122 •int long、float、double、decimaluint long、ulong、float、double、decimallong、ulong float、double、decimalfloat doublechar ushort、int、uint、long、ulong、float、double 、decimal注意,只能从较小的整数类型隐式地转换为较大的整数类型,不能从较大的整数类型隐式地转换为较小的整数类型。也可以在整数和浮点数之间转换,其规则略有不同,可以在相同大小的类型之间转换,例如 int/uint 转换为 float,long/ulong 转换为 double,也可以从 long/ulong转换回 float。这样做可能会丢失 4 个字节的数据,但这仅表示得到的 float 值比使用 double 得到的值精度低,编译器认为这是一种可以接受的错误,而其值的大小是不会受到影响的。无符号的变量可以转换为有符号的变量,只要无符号的变量值的大小在有符号的变量的范围之内即可。2. 显式转换方式有许多场合不能隐式地转换类型,否则编译器会报告错误。下面是不能进行隐式转换的一些场合:● int 转换为 short—— 会丢失数据● int 转换为 uint—— 会丢失数据● uint 转换为 int—— 会丢失数据● float 转换为 int—— 会丢失小数点后面的所有数据● 任何数字类型转换为 char—— 会丢失数据● decimal 转换为任何数字类型— — 因为 decimal 类型的内部结构不同于整数和浮点数但是,可以使用 cast 显示执行这些转换。在把一种类型转换为另一种类型时,要迫使编译器进行转换。类型转换的一般语法如下:long val = 30000;int i = (int)val; // A valid cast. The maximum int is 2147483647这表示,把转换的目标类型名放在要转换的值之前的圆括号中。对于熟悉 C 的程序员来说,这是数据类型转换的典型语法。对于熟悉 C++特殊数据类型转换关键字(如 static_cast)的程序员来说,这些关键字是 C#中不存在,必须使用旧的 C 风格的语法。这种类型转换是一种比较危险的操作,即使在从 long 转换为 int 这样简单的转换过程中,如果原来 long 的值比 int 的最大值还大,就会出问题:long val = 3000000000;int i = (int)val; // An invalid cast. The maximum int is 2147483647在本例中,不会报告错误,也得不到期望的结果。如果运行上面的代码,结果存储在 i 中,则其值为:–1294967296最好假定显式数据转换不会给出希望的结果。如前所述,C#提供了一个 checked 运算符,第 5 章 运算符和类型强制转换 • 123 •使用它可以测试操作是否会产生算术溢出。使用这个运算符可以检查数据类型转换是否安全,如果不安全,就会让运行时抛出一个溢出异常:long val = 3000000000;int i = checked ((int)val);记住,所有的显式数据类型转换都可能不安全,在应用程序中应包含这样的代码,处理可能失败的数据类型转换。第 11 章将使用 try 和 catch 语句引入结构化异常处理。使用数据类型转换可以把大多数数据从一种基本类型转换为另一种基本类型。例如:double price = 25.30;int approximatePrice = (int)(price + 0.5);这么做的代价是把价格四舍五入为最接近的美元数。但在这个转换过程中,小数点后面的所有数据都会丢失。因此,如果要使用这个修改过的价格进行更多的计算,最好不要使用这种转换;如果要输出全部计算完或部分计算完的近似值,且 如 果 不希望用小数点后面的数据去麻烦用户,这种转换是很好的。这个例子说明了把一个无符号的整数转换为 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 int ApproxPrice;}//.double[] Prices = { 25.30, 26.20, 27.40, 30.00 };ItemDetails id;id.Description = "Whatever";id.ApproxPrice = (int)(Prices[0] + 0.5);使用显式的数据类型转换方式,并小心使用这种技术,就可以把简单的值类型的任何实例转换为另一种类型。但在进行显式的类型转换时有一些限制,例如值类型,只能