在《C++学习笔记(13)——类的类型转换》一文中已经对类型转换做了总结,我们已经知道包括基本类型、自定义类在内的左右类型都可以互相转换,但转换需要某些前提条件才能完成。比如可以通过构造函数完成内置类型到类的转换,通过定义转换函数完成类向内置类型的转换。那么,类型转换发生在类与类之间会有什么发生?
一、类的类型转换存在的问题
如果两个根本没有任何关系的类根本没有转换的意义,但是从语法上讲,下述通过指针转换是没问题的:
A a;
A *pa = &a;
B *pb = (B *)pa;
这里的A类和B类没有任何关系,这段代码试图将A类的对象转换为B的指针指向的对象。由于是通过指针转换的,使得编译器顺利编译通过。
再看另一种情况,A和B存在继承关系,A派生出B,则上述转换同样可以编译通过,但是出现了新问题,这种转换是不安全的。由于pa是指向基类的指针,想强制转换为B类指针,但可能B中定义了A中没有成员,这在运行时将发生错误。
那么如何规避这种错误?C++提供了运行阶段的转换判断(RTTI)。
二、C++新式转换
我们已经总结过,C++语言强制类型转显示转换的语法为:
(type-id)expression//转换格式1
type-id(expression)//转换格式2
风格一实际上是C语言的“旧格式”,第二种格式是纯粹的C++。新格式的想法是,要让强制类型转换就像是函数调用。这样的内置类型大的强制类型转换就像是为用户定义的类设计的类型转换。Stroustrup认为,C语言式的强制类型转换由于有过多的可能性而极其危险,就像本文中第一节分析的问题一样。
C++引入可4个强制类型转换运算符,对它们的使用要求更为严格,在这四个运算符为static_cast、dynamic_cast、const_cast、reinterpret_cast,主要运用于继承关系类间的强制转化。
三、dynamic_cast
dynamic_cast是4个转换中唯一的RTTI操作符,提供运行时类型检查。用于在集成体系中进行安全的向下转换。dynamic_cast不是强制转换,而是带有某种”咨询“性质的,如果不能转换,返回NULL。这是强制转换做不到的。注意,源类中必须要有虚函数,保证多态,才能使用dynamic_cast。其格式为:
dynamic_cast<source>(expression)
如上述转换:
A a;
A *pa = &a;
B *pb;
if (pb = dynamic_cast<B *>(pa))
{
cout<<pb->FuncB()<<endl;
}
程序将无输出,因为这是pb为NULL。也即只有将派生类指针转换为基类指针时,上述转换才可以转换成功。
B b;
A *pb = &b;
A *pa;
if (pa = dynamic_cast<B *>(pb))
{
cout<<pa->func()<<endl;
}
上述代码可以成功调用B中重新定义的func函数。
四、static_cast
TypeName val = static_cast<TypeName>(expression);
以上转换只有在TypeName可被隐式转换为expression所属类型,或expression可被隐式转换为TypeName所属类型时,上述转换才合法,否则将出错。如A和B如果不存在继承关系,则无法隐式转换,则用static_cast修饰后,编译器则报错。
五、const_cast
TypeName val = const_cast<TypeName>(expression);
这里要求TypeName和expression的类型完全相同,该运算符只改变值为const或volatile。
A *pa = const_cast<A *>(pb); //这里的A为基类,B为派生类。编译器将报错,指出无法转换,但用dynamic_cast和static_cast都可以完成转换。
const_cast存在的意义是可能存在一个大多数时候是常量的值,但是在某些时候需要修改它。则可以在声明该变量时用const修饰,等到需要修改时用const_cast进行转换。转换得到的变量则被去掉了const限制。需要特别注意,const_cast只可以用于去除指针和引用的常量性,不能去除变量的常量性。
const int a = 10; //a的值不允许修改
int b = 20;
const int *p = &a; //被const修饰,不能使用该指针修改其指向内容的值
int *q;
q = const_cast<int *>(p); //去除p的常量性给q,如果不去除直接赋值会报错
*q = 20;
对变量a加上了const属性,因此a的值是不会被程序所改变的。即使我们将指向a的指针的const属性去掉重新赋值,也不会改变。但是会出现变量a和指针q指向地址相同,但值不同的现象。这看似很奇怪,你可能需要查一下“编译器对常量的优化”。
六、reinterpret_cast
TypeName val = reinterpret_cast<TypeName>(expression);
reinterpret_cast是一种非常强的强制类型转换,它可以将任意两个无关的指针或引用进行转换。它会产生一个新的值,这个值会有与原始参数(expressoin)有完全相同的比特位。按照reinterpret的字面意思“重新解释”,即对数据的比特位重新解释。reinterpret_cast用在任意指针(或引用)类型之间的转换;以及指针与足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。典型的应用是把组数据按照协议结构体进行转换:
struct DATA
{
unsigned int a:8;
unsigned int b:4;
unsigned int c:4;
unsigned int d:16;
};
unsigned int aaa = 0x12345678;
DATA *data = reinterpret_cast<DATA *>(&aaa);
cout<<hex<<data->a<<" "<<data->b<<" "<<data->c<<" "<<data->d<<endl;
输出为78 6 5 1234