C++类型转换在实际编程中会经常使用,其实,本质上对象的类型用来解释(interpret)对象。因为,每个对象都占据一块内存空间,这块内存空间存放了一段二进制数据。通过标记该对象的类型,告诉如何看待这块二进制数据。而对象数据类型转换,就是将源类型解释的内存空间转换为目标类型来解释这块内存空间。
- 传统转换方式(Traditional Type-Casting)是沿用c的方式,通过强制类型转换,隐式转换,显示转换等方式来标识内存空间的解释模式,这种方式功能强大,约束少,但存在安全隐患。因此,通过 static_cast, dynamic_cast, reinterpret_cast,以一种更安全的方式来完成传统转换方式的替换。
- static_cast 是在编译时就对类型进行检查和转换,是对传统转换的一种规范的表现形式。它会编译期检查源类型与目标类型的关系,以确定这种转换可否实现。
- dynamic_cast是在运行时动态对源类型和目标类型进行检查,以确定转换可否实现,并且还会进行源类型对象和目标类型对象的完整性检查。
- reintepret_cast是一种"野蛮"的强行转换,它不过多进行源对象类型和目标对象类型的检查,只将源对象内存空间按照目标对象类型进行解释即可。
一般在转换过程中可以通过如下几种方式来实现:
转换类型 | 应用场景和使用 |
传统的转换方式(C式或函数式) | 使用在基本数据类型和自定义对象类型转换,各种类型的强制转换等场合,使用范围很广,缺点是存在安全隐患,因此提供其他四种类型转换在特定的场合来做替代。 这也是延续C的形式,当然这类转换也是可以用static_cast来替换,但是因为是基本类型,所以 传统转换已经很直观 |
static_cast转换运算符 | 用在特定场景 对象转换 之上,static_cast会调用相应的构造函数或者重载的转换运算符[不包括赋值运算符]。对于单参构造函数的存在可能会引发一些隐式的转换,因此用static_cast也可以明确的指出类型的转换过程,避免生成多余的临时对象造成效率下降(Google C++ Style Guide) [虽然static_cast也可以用在有继承关系的类型指针之间,但是还是将这方面的转换交给dynamic_cast来操作吧] |
dynamic_cast转换运算符 | 将其用在具有继承关系的指针类型之间的转换。 无论是从基类到子类的转换,还是子类到基类的转换,都将dynamic_cast套上去,也算是标识它们是一家子 |
reinterpret_cast转换运算符 | 一般用在将对象指针类型转换到整数类型或者void * (空指针)。 需要注意的是,若要使用其结果,一定要将类型转换回去后使用,否则会存在隐患。也不要将随意的整数转换成指针类型 |
const_cast转换运算符 | 用在需要去除掉const限定的时候。 其实这种情况出现的很少,可能的方法在const_cast一文中已经又举例,不过还是反复强调, 使用const_cast转换后,绝对不可试图修改结果的值 |
如果任何一种基于指针或引用的转换,套上四个转换运算符之后都失败,那么所要进行的转换可能就触到了"雷区"了:进行了没意义的转换。比如,对于没有关系的两个类型的指针进行了转换,比如试图转换指向方法的指针了。所以转换运算符对于避免代码出错也很有帮助。
下面我们一一分析上述转换类型
一、传统的转换方式(Traditional Type-Casting)
C++作为C语言的超集,完全继承了C语言所具有的类型转换方法和能力,因此对于这部分在基础数值类型上的转换是比较容易理解的。但是因为C++是面向对象的语言,有类的概念,因此让又多一层需要理解的内容。
- 1. 隐式转换 (Implicit Conversion)
隐式转换不需要任何转换运算符,编译器会自动根据类型兼容性进行不同类型之间的转换。
- 2. 显示转换 (Explicit Conversion)
显示转换要表明所要转换的目标对象是什么样的类型,然后编译器会做出转换,它有两种格式:
-
- C语言格式(C-like Cast)
(new_type) expression
-
- 函数式(Function-style Cast)
new_type (expression)
二、static_cast <new_type> (expression)
- 1. 和传统转换类型一样,static_cast可以对基础类型和对象进行类型转换。而基础类型和对象的转换都是其他三个转换运算符所办不到的。
这些转换跟传统类型转换比较类似,即传统转换均可加上static_cast。
static_cast会根据下述顺序寻找合适的方法进行类型转换。
-
- <1>. 构造函数(Constructor)
- <2>. 类型转换运算符(Type –Cast Operator)
*赋值运算符并不被static_cast作为实际的类型转换使用,因为它自身已经是一种运算符,不能再当做转换运算符来用。
- 2. static_cast不仅可以用在基本数据类型和对象上,还可以用在指针和引用上。
在指针和引用方面,只有继承关系是可以被static_cast接受的,其他情况的指针和引用转换都会被static_cast直接扔出编译错误,如下代码。
这就要通过reinterpret_cast<type>()来完成了。
class A{ public: int m_a; } int main(){ A* pa = new A(); int ia = static_cast<long>(pa); return 0; }
error: invalid static_cast from type ‘A*’ to type ‘long int’
int ia = static_cast<long>(pa);
^
继承关系类型间转换可以通过static_cast,但会存在安全隐患,实际上这层关系上的转换又几乎都可以被dynamic_cast所代替。
因此,static_cast<type>()用在特定场景对象转换之上,static_cast会调用相应的构造函数或者重载的转换运算符[不包括赋值运算符]。对于单参构造函数的存在可能会引发一些隐式的转换,因此用static_cast也可以明确的指出类型的转换过程,避免生成多余的临时对象造成效率下降.[虽然static_cast也可以用在有继承关系的类型指针之间,但是还是将这方面的转换交给dynamic_cast来操作吧]
三、dynamic_cast <new_type> (expression)
dynamic_cast是动态类型转换,及对象类型的转换过程发生在运行时,如果转换不成功就返回null,转换成功返回目标对象。dynamic在转换的过载中会对源对象和目标对象类型做完整性检查。也就是目标对象的内存模型必须 >= 源目标对象的内存模型
- 1. 子类 -->基类 ,static_cast和dynamic_cast都是成功并且正确的(所谓成功是说转换没有编译错误或者运行异常;所谓正确是指方法的调用和数据的访问输出是期望的结果),这是面向对象多态性的完美体现。
- 2. 基类 -->子类 , static_cast和dynamic_cast都是成功的,但是正确性方面,我对两者的结果都先进行了是否非空的判别:
- dynamic_cast的结果显示是空指针,
- 而static_cast则是非空指针。
但很显然,static_cast的结果应该算是错误的,子类指针实际所指的是基类的对象,而基类对象并不具有子类的Study()方法。
- 3. 对于没有关系的两个类之间的转换,输出结果表明,
- dynamic_cast依然是返回一个空指针以表示转换是不成立的;
- static_cast直接在编译期就拒绝了这种转换。
- reinterpret_cast成功进行了转换,而且返回的值并不是空指针,但是结果显然是错误的。
最后,程序里还用dynamic_cast希望把用其他转换运算符转换过去的指针转换回来。对于使用static_cast转换后指向了子类对象的基类指针,dynamic_cast判定转换是合理有效的,因此转换成功获得一个非空的指针并且正确输出了结果;而对于reinterpret_cast转换的类型,的确如它的功能一样——重新解析,变成新的类型,所以才得到dynamic_cast判定该类型已经不是原来的类型结果,转换得到了一个空指针。
四、reinterpret_cast <new_type> (expression)
reinterpret_cast运算符是用来处理无关类型之间的转换;它会产生一个新的值,这个值会有与原始参数(expressoin)有完全相同的内存空间。
也就是说,reinterpret_cast会将源对象的内存空间按照目标对象的类型格式进行解释,以将源类型对象转换目标类型对象。reinterpret_cast的字面意思:重新解释(类型的内存空间)。转换后的对象是否合法和正确、可用,reinterpret并不做保证。
五、const_cast <new_type> (expression)
用const_cast来去除const限定,使用const_cast去除const限定的目的绝对不是为了修改它的内容。
六、总结
以上分别介绍了各种类型转换,及其可能使用的场景。
总得说来,static_cast和reinterpret_cast运算符要么直接被编译器拒绝进行转换,要么就一定会得到相应的目标类型的值。而dynamic_cast却会进行判别,确定源指针所指的内容,是否真的合适被目标指针接受。如果是否定的,那么dynamic_cast则会返回null。这是通过检查"运行期类型信息"(Runtime type information,RTTI)来判定的,它还受到编译器的影响,有些编译器需要设置开启才能让程序正确运行(导师的PPT详细介绍了Visual Studio的情况),因此dynamic_cast也就不能用传统的转换方式来实现了。
参考 :
[1]. Type conversions. http://www.cplusplus.com/doc/tutorial/typecasting/
[2]. C++标准转换运算符. http://www.cnblogs.com/ider/archive/2011/07/22/cpp_cast_operator_part2.html
[3]. C++用户自定义转换(User-Defined Conversion). http://www.cnblogs.com/ider/archive/2011/07/03/cpp_cast_operator_part1.html
[4]. C++标准转换运算符dynamic_cast. http://www.cnblogs.com/ider/archive/2011/08/01/cpp_cast_operator_part5.html