C++中的类型转换
C++类型转换分为:隐式类型转换和显式类型转换
1.隐式转换
1) 算术转换(Arithmetic conversion)。在混合类型的算术表达式中, 最宽的数据类型成为目标转换类型。
double dval = 3.14159;
ival + dval;//ival被提升为double类型
2)赋值转换。一种类型表达式赋值给另一种类型的对象:目标类型是被赋值对象的类型
ival = dval; // double->int
例外:void指针赋值给其他指定类型指针时,不存在标准转换,编译出错。必须强制转换,例如使用malloc()函数
3)将一个表达式作为实参传递给函数调用,此时形参和实参类型不一致:目标转换类型为形参的类型
cout << "The square root of 2 is " << sqrt(2) << endl;//2被提升为double类型:2.0
4)从一个函数返回一个表达式,表达式类型与返回类型不一致:目标转换类型为函数的返回类型
{
return ival1 - ival2;//返回值被提升为double类型
}
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<int>(a); char c = static_cast<char>(b); type = static_cast<test_enum>(b); char* pa = NULL; int *pb = (int*)pa; //int *pb = static_cast<int*>(pa); //error char *pc = (char*)pb; //char *pc = static_cast<char*>(pb); //error void *p = static_cast<void*>(pa); pb = static_cast<int*>(p); pc = static_cast<char*>(p);
class A { public: void print(){cout<<"A"<<endl;} }; class B:public A { public: void print(){cout<<"B"<<endl;} }; void main(void) { A a; typedef void (A::*PS_MFunc)(); //函数指针指向类A的成员函数指针 PS_MFunc func = &A::print; (a.*func)(); func = static_cast<PS_MFunc>(&B::print); //函数指针指向子类B成员函数,必须进行转换 (a.*func)(); }
输出A,B
上行转换:子类(指针、引用、类型)转换成基类(指针、引用、类型)。不管是显式或者隐式,都可行。安全
下行转换:基类指针转换成子类指针——危险(没有动态类型检查)
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(); <span style="white-space:pre"> </span> a =b;//正确。 a=static_cast<A>(b); a = static_cast<A&>(b); pa=pb; pa = static_cast<A*>(pb); //right 基类指针指向子类 b=a;//错误 b=static_cast<B>(a);//错误 b=static_cast<B&>(a);//正确 pb=pa;//错误 pb=static_cast<B*>(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<A>(b);//错误 a = dynamic_cast<A&>(b); //正确 pa=pb;//正确 pa = dynamic_cast<A*>(pb); //right 基类指针指向子类 b=a;//错误 b=dynamic_cast<B>(a);//错误 b=dynamic_cast<B&>(a);//正确,需要具备虚函数机制 pb=pa;//错误 pb=dynamic_cast<B*>(pa);//正确,需要具备虚函数机制 b=c;//错误 b=dynamic_cast<B>(c);//错误 b=dynamic_cast<B&>(c);//正确,不需要具备虚函数机制 pb=pc;//错误 pb=dynamic_cast<B*>(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<Derived *>(pb); Derived *pd2 = dynamic_cast<Derived *>(pb); }如果pb实际指向一个Derived类型的对象,pd1和pd2是一样的,并且对这两个指针执行Derived类型的任何操作都是安全的;如果pb实际指向的是一个Base类型的对象,那么pd1将是一个指向该对象的指针,对它进行Derived类型的操作将是不安全的(如访问m_szName),而pd2将是一个空指针(即0,因为dynamic_cast失败)。
Base要有虚函数,否则会dynamic_cast下行转换编译出错;static_cast则没有这个限制。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。
例3:dynamic_cast常见使用与虚函数机制
class A{ public : virtual void func1(){cout<<"A---func1"<<endl;} void func2(){cout<<"A-func2"<<endl;} virtual ~A(){} }; class B:public A{ public: void func1(){cout<<"B---func1"<<endl;} void func2(){cout<<"B-func2"<<endl;} void func3(){cout<<"B-func3"<<endl;} }; void main(void) { A *pa=new A; pa->func1(); pa->func2(); B*pb=dynamic_cast<B*>(pa); if(pb) pb->func1(); pb->func2(); pb->func3(); delete pa; cout<<"------------------------"<<endl; pa=new B; pa->func1(); pa->func2(); pb=dynamic_cast<B*>(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<B>(b1); b2. m_iNum = 200; //fine }代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象。
参考:
2.static_cast, dynamic_cast, const_cast探讨