首页 > 代码库 > 数据类型(2) - 类型转换
数据类型(2) - 类型转换
类型转换分为三种,一种是值类型之间的,一种是引用类型之间的,而最后一种则是值类型和引用类型的相互转换。
值类型之间的转换比较简单,通常是数字的转换。而c#的可以代表数字的若干类型的区别仅仅是其取值范围不同,例如short,int,long之类。此时如果要将取值范围较小的数据类型转为取值范围较大的类型,则一般一定会成功(我想不出失败的理由),无需写出类型名称,直接就可以完成隐式转换。例如将Short转为int。但如果反过来将int转为short,则隐式转换一定会失败,编译器会通知我们这个转换必须使用显式转换。此时我们需要在括号中显式指出转换类型名称(即使你知道转换一定会成功):
int aa = 0; short bb = (short) aa;
显式转换是强制的,所以如果当实际上转换不能成功时(例如将65537转换为short),将会保存溢出的值。比如将65537转换为short,你将会得到值1。默认情况下,溢出不会抛出异常。如果你想捕捉可能的溢出并进行处理,可以使用checked关键字,括住要检查的转换语句,或者{}也可以。此时如果转换出现溢出,则会抛出异常。可以用try-catch处理异常。Visual studio在项目属性中build的advanced中,提供了check for arithmetic overflow/underflow一项,勾选它意味着自动为所有的转换运算加上check。我们无需一个一个自己加。
当然,如果你启用了check for arithmetic overflow/underflow,但对于某一块代码不想抛出异常,你可以用unchecked关键字包住这块代码。因为不抛出异常是编译器的默认行为,所以unchecked关键字只有在全局启用了check for arithmetic overflow/underflow才显得有用。
引用类型之间的转换也很常见。对于父类,子类之间的继承,我们可以类比值类型的short和int。但情况比值类型来说要稍微难懂一些。比如假设我们有一个类rectangle(构造函数吃三个变量),我之前曾经对于下面这行代码完全无法理解:
object c = new rectangle("", 1, 2);
那么c到底是什么类型呢?假设类rectangle有一个方法area,返回长方形的面积,那么我们可以试图调用c.area,此时编译器报错,我们看到其实c是不存在这个方法的,所以c应该是object类型。但我调用c.GetType(),返回的却是rectangle。那么这行代码执行之后,究竟发生了什么事情?实际上是这样的:
首先,c是object类型这个肯定没有问题(否则c将拥有所有rectangle的方法,而实际上没有)。当执行到new之前,系统在栈上分配一块地盘,为c的引用。(此时还没有指针)执行new的时候,系统在堆上分配另外一块地盘(其大小等于rectangle所有值类型属性+引用类型属性的大小之和),然后将栈上的指针指到堆上这块地盘上去。而GetType()返回的类型,并不是栈上那块引用的类型,而是这个栈指向的堆上那块地盘的类型。此时,由于引用本身和指针指向的终点的类型不同,但前者较大,所以隐式转换没有问题。
那么问题来了,如果我反过来如何呢?
rectangle d = new object();
猜也能猜得出,这显然是不行的。如果你打算显式转换的话,你仍然可以通过指定转换类型来完成:
object x = new object(); rectangle y = (rectangle)x;
不过这次和值类型不一样,如果失败了的话,编译器可不知道怎么处理溢出(实际上溢出的概念是专指数字的),所以将会爆出运行时错误!try-catch当然是一个解决方案,但c#还提供了as关键字进行快速检测:
rectangle d = x as rectangle;
此时如果d的值为null,则意味着转换失败。你可以在其后进行处理,并不会抛出任何异常。
值类型和引用类型的相互转换被称为装箱和拆箱。根据之前所讲,这种转换肯定是最复杂的,因为两种类型的组件个数都不同。所以,当值类型转换为引用类型(装箱)时,我们要在堆上额外分配空间,还要加一个指针。具体点说就是:
- 计算应该在堆上分配内存的大小,注意这个大小包括了额外的指针
- 在堆上分配内存
- 返回值类型的地址(现在栈上的值类型变成了对堆上的对象的一个引用)
拆箱较为简单:
- 获得目标对象在堆上的所有属性的地址
- 将这些属性包含的值(通过地址获得)复制到栈上的值类型实例之中
int i=0;System.Object obj=i;Console.WriteLine(i+","+(int)obj);
这个例子中一共发生了2次装箱和一次拆箱。拆箱是第三行将obj转换为int类型,2次装箱都发生在WriteLine方法中。当试图构建一个字符串时,系统隐式的调用了concat方法,其所有的变量都是object。所以i和(int32)obj全部都需要装箱。
注意“,”已经是一个字符串,而字符串是引用类型。引用类型永远是装箱的所以不需要再次装箱。
装箱和取消装箱都是需要大量运算的过程。 对值类型进行装箱时,必须创建一个全新的对象。 此操作所需时间可比简单的引用赋值操作长 20 倍。 取消装箱时,强制转换过程所需时间可达赋值操作的四倍。(http://msdn.microsoft.com/zh-cn/library/ms173196.aspx)在Array这种你可以往里面放任何东西的对象集合中,所有的成员都是object,遍历他并进行修改可能的装箱拆箱次数非常大,性能会受到严重的影响。c#在2.0版本提出了泛型以取代这种弱类型集合对象(DataTable是另外一个例子)。
数据类型(2) - 类型转换