隐式转换
隐式转换不需要做任何工作,也不需要另外编写代码。考虑下面的代码:
var1 = var2;
如果 var2
的类型可以隐式地转换为 var1
的类型,这个赋值语句就涉及一个隐式转换。这两个变量的类型也可能相同,此时就不需要隐式转换。例如,ushort
和 char
的值是可以互换的,因为它们都可以存储 0~65535 之间的数字,在这两个类型之间可以进行隐式转换,如下面的代码所示:
ushort destinationVar; char sourceVar = 'a'; destinationVar = sourceVar; Console.WrileLine("sourceVar val: {0}", sourceVar); Console.WriteLine("destinationVar val: {0}", destinationVar);
这里存储在 sourceVar
中的值放在 destinationVar
中。在用两个 Console.WriteLine( )
命令输出变量时,得到如下结果:
sourceVar val : a destinationVar val : 97
即使两个变量存储的信息相同,使用不同的类型解释它们时,方式也是不同的。
简单类型有许多隐式转换:bool
和 string
没有隐式转换,但数值类型有一些隐式转换。表 5-1
列出了编译器可以隐式执行的数值转换(记住,char
存储的是数值,所以 char
被当作一个数值类型)。
表5-1 隐式数值转换
类型 | 可以安全地转换为 |
---|---|
byte | short,ushort,int,uint,long,ulong,float,double,decimal |
sbyte | short,int,long,float,double,decimal |
short | int,long,float,double,decimal |
ushort | int,uint,long,ulong,float,double,decimal |
int | long,float,double,decimal |
uint | long,ulong,float,double,decimal |
long | float,double,decimal |
ulong | float,double,decimal |
float | double |
char | ushort,int,uint,long,ulong,float,double,decimal |
不要担心--不需要记住这个表格,因为很容易看出编译器可以执行哪些隐式转换。第3章中表3-6
列出了每种简单数学类型的取值范围。这些类型的隐式转换规则是:任何类型 A,只要其取值范围完全包含在类型 B 的取值范围内,就可以隐式转换为类型 B。
其原因是很简单的。如果要把一个值放在变量中,而该值超出了变量的取值范围,就会出问题。例如,short
类型的变量可以存储 0~32767 的数字,而 byte
可以存储的最大值是 255,所以如果要把一个 short
值转换为 byte
值,就会出问题。如果 short
包含的值在 256~32767 之间,相应数值就不能放在 byte
中。
但是,如果 short
类型变量中的值小于 255,就应能转换这个值吗?答案是可以。具体地说,虽然可以,但必须使用显式。执行显式转换有点类似于 “我已经知道你对我这么做提出了警告,但我将对其后果负责”。
显示转换
顾名思义,在明确要求编译器把数值从一种数据类型转换为另一种数据类型时,就是在执行显式转换。因此,这需要另外编写代码,代码的格式因转换方法而异。在学习显式转换代码前,首先分析如果不添加任何显式转换代码,会发生什么情况。
例如,下面对上一节的代码进行修改,试着把 short
值转换为 byte
:
byte destinationVar; short sourceVar = 7; destinationVar = sourceVar; Console.WriteLine("sourceVar val: {0}", sourceVar); Console.WriteLine("destinationVar val: {0}", destinationVar);
如果编译这段代码,就会产生如下错误:
Cannot imp;icitly convert type 'short' to 'byte!'. An explicit conversion exists (are you missing a cast?)
幸运的是,C#编译器可以检测没有进行显式转换!
为了成功编译这段代码,需要添加代码,进行显式转换。最简单的方式是把 short
变量强制转换为 byte
(由上述错误字符串提出)。强制转换就是强迫数据从一种类型转换为另一种类型,其语法比较简单:
(<destinationType>)<sourceVar>
这将把 <sourceVar>
中的值转换为 <destinationType>
。
这只在某些情况下是可行的。彼此之间几乎没有什么关系的类型或根本没有关系的类型不能进行强制转换。
因此可以使用这个语法修改示例,把 short
变量强制转换为 byte
:
byte destinationVar; short sourceVar = 7; destinationVar = (byte)sourceVar; Console.WriteLine("sourceVar val: {0}", sourceVar); Console.WriteLine("destinationVar val: {0}", destinationVar);
得到如下结果:
sourceVar val: 7 destinationVar val: 7
在视图把一个值转换为不兼容的变量时,会发生什么呢?以整数为例,不能把一个大整数放到一个太小的数字类型中。如下所示修改代码就能够证明这一点:
byte destinationVar; short sourceVar = 281; destinationVar = (byte)sourceVar; Console.WriteLine("sourceVar val: {0}", sourceVar); Console.WriteLine("destinationVar val: {0}", destinationVar);
结果如下:
sourceVar val: 281 destinationVar val: 25
发生了什么?看看这两个数字的二进制表示,以及可以存储在 byte
中的最大值 255:
281 = 1 0001 1001 25 = 0 0001 1001 255 = 0 1111 1111
可以看出,源数据的最左边一位丢失了。这会引发一个问题:如何确定数据是何时丢失的?显然,当需要显式地把一种数据类型转换为另一种数据类型时,最好能够了解是否有数据丢失了。如果不知道这些,就会发生严重问题,例如,会计应用程序或确定火箭飞往月球的轨道的应用程序。
一种方式是检查源变量的值,将它与目标变量的取值范围进行比较。还有另一个技术,及时迫使系统特别注意运行期间的转换。在将一个值放在一个变量中时,如果该值过大,不能放在该类型的变量中,就会导致溢出,这就需要检查。
对于为表达式设置所谓的溢出检查上下文,需要用到两个关键字 checked
和 unchecked
。按下述方式使用这两个关键字:
checked(<expression>) unchecked(<expression>)
下面对上一个示例进行溢出检查:
byte destinationVar; short sourceVar = 281; desinationVar = checked((byte)sourceVar); Console.WriteLine("sourceVar val: {0}", sourceVar); Console.WriteLine("destinationVar val: {0}", destinationVar);
执行这段代码时,程序会崩溃,并显示错误信息。
但在这段代码中,如果用 unchecked
替代 checked
,就会得到与以前同样的结果,不会出现错误。这与前面的默认做法是一样的。
使用Convert命令进行显式转换
前面装节中的许多 “试一试”示例中使用的显式类型转换,与本章前面的示例有一些区别。前面使用 Convert.ToDouble()
等命令把字符串值转换为数值,显然,这种方式并不适用于所有字符串。
为成功执行此类转换,所提供的字符串必须是数值的有效表达方式,该数还必须是不会溢出的数。数值的有效表达方式是:首先是一个可选符号(加号或减号),然后是 0 位或多位数字,一个可选的句点后跟一位或多位数字,接着是一个可选的 e
或 E
,后跟一个可选符号和一位或多位数字,除了还可能有空格(在这个序列之前或之后),不能有其他字符。利用这些可选的额外数据,可将 -1.2451e-24 这样复杂的字符串识别为一个数值。
按这种方式可以进行许多显式转换,如表 5-2 所示
。
表5-2 转换命令
命令 | 结果 |
---|---|
Convert.ToBoolean(val) |
val 转换为 bool
|
Convert.ToByte(val) |
val 转换为 byte
|
Convert.ToChar(val) |
val 转换为 char
|
Convert.ToDecimal(val) |
val 转换为 decimal
|
Convert.ToDouble(val) |
val 转换为 double
|
Convert.ToInt16(val) |
val 转换为 short
|
Convert.ToInt32(val) |
val 转换为 int
|
Convert.ToInt64(val) |
val 转换为 long
|
Convert.ToSByte(val) |
val 转换为 sbyte
|
Convert.ToSingle(val) |
val 转换为 float
|
Convert.ToString(val) |
val 转换为 string
|
Convert.ToUInt16(val) |
val 转换为 ushort
|
Convert.ToUInt32(val) |
val 转换为 uint
|
Convert.ToUInt64(val) |
val 转换为 ulong
|
其中 val
可以是大多数变量类型(如果这些命令不能处理该类型的变量,编译器就会告诉用户)。
但 如表 5-2 所示
,转换的名称略不同于C#类型名称,例如,要转换为 int
应使用 Convert.ToInt32()
。这是因为这些命令来自 .NET Framework 的 System 名称空间,而不是本机C#本身。这样它们就可以在除C#外的其他 .NET 兼容语言中使用。
对于这些转换要注意的一个问题是,它们总是要进行溢出检查,checked
和 unchecked
关键字以及项目属性设置不起作用。
下面的示例包括本节介绍的许多转换类型。它声明和初始化许多不同类型的变量,再在它们之间进行隐式转换。
把下述代码添加到 Program.cs
中:
static void Main(string[] args) { short shortResult, shortVal = 4; int integerVal = 67; long longResult; float floatVal = 10.5f; double doubleResult, doubleVal = 99.999; string stringResult, striingVal = "17"; bool boolVal = true; Console.WriteLine("Variable Conversion Examples\n"); doubleResult = floatVal * shortVal; Console.WriteLine("Implicit, -> double: {0} * {1} -> {2}", floatVal, shortVal, doubleResult); shortResult = (short)floatVal; Console.WriteLine("Explicit, -> short: {0} -> {1}", floatVal, shortResult); stringResult = Convert.ToString(boolVal) + Convert.ToString(doubleVal); Convert.WriteLine("Explicit, -> string: \"{0}\" + \"{1}\" -> {2} ", boolVal, doubleVal, stringResult); longResult = integerVal + Convert.ToInt64(stringVal); Console.WriteLine("Mixed, -> long: {0} + {1} -> {2}", integerVal, stringVal, longResult); Console.ReadKey(); }
示例的说明 这个示例包含前面介绍的所有转换类型,既有像前面简短代码示例中的简单赋值,也有在表达式中进行转换。必须考虑这两种情况,因为每个非一元运算符的处理都可能要进行类型转换,而不仅仅是赋值运算符。例如:
shortVal * floatVal
其中把一个
short
值与一个float
值相乘。在这样的指令中,没有指定显式转换,所以如有可能,就会进行隐式转换。在这个示例中,唯一有意义的隐式转换是把short
值转换为float
(因为把float
值转换为short
需要进行显式转换),所以这里将使用隐式转换。 也可以覆盖这种行为,如下所示:
shortVal * (short)floatVal
有趣的是,两个
short
相乘的结果并不会返回一个short
值。因为这个操作的结果很可能大于 32767 (这是short
可以包含的最大值),所以这个操作的结果实际上是int
。
使用这个数据类型转换语法执行显式转换,其运算符的优先级与其他一元运算符一样,都是优先级中的最高级,如 ++ (用作前缀)。
如果语句涉及混合类型,就根据运算符的优先级,在处理每个运算符时执行转换。这意味着可能出现 “中间” 转换,例如:
doubleResult = floatVal + (shortVal * floatVal);
要处理的第一个运算符是
*
,如上所述,它将把shortVal
转换为float
。接着处理+
运算符,它不需要进行任何转换,因为这是把两个float
值相加(floatVal
和shortVal * floatVal
的float
结果)。在最后处理=
运算符时,这个计算的float
结果转换为double
。
这个转换过程初看起来比较复杂,但只要按照运算符的优先级把表达式分解为不同的部分,就可以弄明白这个过程。