C++类型转换分为:隐式类型转换和显式类型转换
1.隐式转换
1) 算术转换(Arithmetic conversion)。在混合类型的算术表达式中, 最宽的数据类型成为目标转换类型。
2)赋值转换。一种类型表达式赋值给另一种类型的对象:目标类型是被赋值对象的类型
例外:void指针赋值给其他指定类型指针时,不存在标准转换,编译出错。必须强制转换,例如使用malloc()函数
3)将一个表达式作为实参传递给函数调用,此时形参和实参类型不一致:目标转换类型为形参的类型
4)从一个函数返回一个表达式,表达式类型与返回类型不一致:目标转换类型为函数的返回类型
2.显式转换
C++继承了C中的隐式和显式转换的方式。但这种转换并不是安全和严格的,加上C++本身对象模型的复杂性,C++(C++是强类型语言)增加了四个显式转换的关键字:static_cast、dynamic_cast、reinterpret_cast和const_cast。
2.1 static_cast
使用方法
static_cast < type-id > ( expression )。该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。
使用说明
(1)用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用或者对象转换成基类表示)是安全的。进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的。 (2)用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。 (3)把void指针转换成目标类型的指针(不安全!),或者将目标类型指针转换为void指针。 (4)把任何类型的表达式转换成void类型。static_cast不能去掉expression的const、volitale、或者__unaligned属性。
例1.基本类型转换
test_enum type = test_enum_1;char a ;int b = static_cast (a);char c = static_cast(b);type = static_cast (b);char* pa = NULL;int *pb = (int*)pa;//int *pb = static_cast (pa); //errorchar *pc = (char*)pb;//char *pc = static_cast (pb); //errorvoid *p = static_cast (pa);pb = static_cast (p);pc = static_cast (p);
例2.类层次中基类与子类成员函数指针的转换
class A{ public: void print(){cout<<"A"<(&B::print); //函数指针指向子类B成员函数,必须进行转换 (a.*func)();}
输出A,B
例3:类层次结构中基类与子类指针或引用之间的转换
上行转换:子类(指针、引用、类型)转换成基类(指针、引用、类型)。不管是显式或者隐式,都可行。安全
下行转换:基类指针转换成子类指针——危险(没有动态类型检查)
class A{};class B:public A{};class C:public A{};class D{};void main(void) { A a; B b; A* pa= new A(); B* pb = new B(); C* pc = new C(); D* pd = new D(); a =b;//正确。 a=static_cast(b); a = static_cast (b); pa=pb; pa = static_cast (pb); //right 基类指针指向子类 b=a;//错误 b=static_cast (a);//错误 b=static_cast (a);//正确 pb=pa;//错误 pb=static_cast (pa);//正确}
总结:
上行转换皆可。下行转换,仅仅指针和引用转换合法,但是没有作安全检查。不能进行交叉转换和非继承关系之间的转换。
2.2 dynamic_cast
使用方法
dynamic_cast < type-id > ( expression )。该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。
使用说明
(1)当无法使用virtual函数的时候,需要dynamic_cast强制转换。尤其是不知道基类原码,但是要在派生类中增加函数,且使用基类指针传入函数参数时。 (2)支持交叉转换。即继承机制中,兄弟之间的指针转换。 (3)只有在基类指针转换为子类指针时才有意义。只有在存在虚函数机制的情况下,才容许下行转换。并且,只有基类指针/引用本身指向的是一个派生类的对象,然后将此基类指针、引用转换为对应的派生类指针、引用才是有意义的。否则即使能够转换,返回也为空,此时应该检查。
例1 类继承中的转换
class A{public : virtual ~A(){}};class B:public A{};class C:public A{};class D{};void main(void) { A a; B b; A* pa= new A(); B* pb = new B(); C* pc = new C(); D* pd = new D(); a =b;//正确。 a=dynamic_cast(b);//错误 a = dynamic_cast (b); //正确 pa=pb;//正确 pa = dynamic_cast (pb); //right 基类指针指向子类 b=a;//错误 b=dynamic_cast (a);//错误 b=dynamic_cast (a);//正确,需要具备虚函数机制 pb=pa;//错误 pb=dynamic_cast (pa);//正确,需要具备虚函数机制 b=c;//错误 b=dynamic_cast (c);//错误 b=dynamic_cast (c);//正确,不需要具备虚函数机制 pb=pc;//错误 pb=dynamic_cast (pc);//正确,需要具备虚函数机制}总结:
上行转换不能进行类型转换。下行转换仅仅在存在虚函数机制下,指针和引用的转换合法。交叉转换,指针转换必须在具备虚函数机制下合法,引用转换不需要。其他转换皆不合法。
为何使用dynamic_cast下行转换类指针时,需要虚函数呢?
Dynamic_cast转换是在运行时进行转换,运行时转换就需要知道类对象的信息(继承关系等)。如何在运行时获取到这个信息——虚函数表。C++对象模型中,对象实例最前面的就是虚函数表指针,通过这个指针可以获取到该类对象的所有虚函数,包括父类的。因为派生类会继承基类的虚函数表,所以通过这个虚函数表,我们就可以知道该类对象的父类,在转换的时候就可以用来判断对象有无继承关系。所以虚函数对于正确的基类指针转换为子类指针是非常重要的。
例2 static_cast和dynamic_cast的区别 dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。上行转换时,dynamic_cast和static_cast的效果是一样的(前者只能进行转换为引用和指针类型,后者还包括类型本身)。
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。但是dynamic_cast要求存在虚函数机制,而static_cast不需要。下行转换仅仅涉及到指针和引用转换。
class Base{public: int m_iNum; virtual void foo();};class Derived:public Base{public: char *m_szName[100];};void func(Base *pb){ Derived *pd1 = static_cast如果pb实际指向一个Derived类型的对象,pd1和pd2是一样的,并且对这两个指针执行Derived类型的任何操作都是安全的;如果pb实际指向的是一个Base类型的对象,那么pd1将是一个指向该对象的指针,对它进行Derived类型的操作将是不安全的(如访问m_szName),而pd2将是一个空指针(即0,因为dynamic_cast失败)。 Base要有虚函数,否则会dynamic_cast下行转换编译出错;static_cast则没有这个限制。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。(pb); Derived *pd2 = dynamic_cast (pb);}
例3:dynamic_cast常见使用与虚函数机制
class A{public : virtual void func1(){cout<<"A---func1"<输出:func1(); pa->func2(); B*pb=dynamic_cast (pa); if(pb) pb->func1(); pb->func2(); pb->func3(); delete pa; cout<<"------------------------"< func1(); pa->func2(); pb=dynamic_cast (pa); if(pb) pb->func1(); pb->func2(); pb->func3(); delete pa;}
如图所示。基类指针体现了运行多态性。且当基类指针指向派生类对象时,重载函数调用基类的版本,且此转换结果为非空。若基类指针指向基类对象,转换结果为空。但是虽然为空,func3()和func2()依然能够正常调用,因为此时没有使用任何成员数据,也不是虚函数,不要this指针和动态绑定,可以正常运行。
总结:
dynamic_cast提供运行时安全检查,因此不能进行某些无理的转换。并不是强制转换(带有某种咨询性质),能转换则转换,不能则返回为空(可以用作检查条件)。
2.3 const_cast
const_cast<type_id> (expression)。该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。
class B{public: int m_iNum;};void foo(){ const B b1; b1.m_iNum = 100; //comile error B b2 = const_cast(b1); b2. m_iNum = 200; //fine}代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象。
参考:
1.
2.