攻城狮凌风

C++中的类型转换

       C++类型转换分为:隐式类型转换和显式类型转换

1.隐式转换

      1) 算术转换(Arithmetic conversion)。在混合类型的算术表达式中, 最宽的数据类型成为目标转换类型。

int ival = 3;
double dval = 3.14159;
ival 
+ dval;//ival被提升为double类型

    

      2)赋值转换。一种类型表达式赋值给另一种类型的对象:目标类型是被赋值对象的类型

int *pi = 0// 0被转化为int *类型
ival = dval; // double->int

         例外:void指针赋值给其他指定类型指针时,不存在标准转换,编译出错。必须强制转换,例如使用malloc()函数
    

     3)将一个表达式作为实参传递给函数调用,此时形参和实参类型不一致:目标转换类型为形参的类型

extern double sqrt(double);
cout 
<< "The square root of 2 is " << sqrt(2<< endl;//2被提升为double类型:2.0

    

    4)从一个函数返回一个表达式,表达式类型与返回类型不一致:目标转换类型为函数的返回类型

double difference(int ival1, int ival2)
{
    
return ival1 - ival2;//返回值被提升为double类型
}

 

2.显式转换

      C++继承了C中的隐式和显式转换的方式。但这种转换并不是安全和严格的,加上C++本身对象模型的复杂性,C++(C++是强类型语言)增加了四个显式转换的关键字:static_castdynamic_castreinterpret_castconst_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);


    例2.类层次中基类与子类成员函数指针的转换

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


例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();
<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是两个不同的对象。

    


参考:

     1.static_cast与dynamic_cast转换

     2.static_cast, dynamic_cast, const_cast探讨








posted on 2015-07-17 15:48  攻城狮凌风  阅读(179)  评论(0编辑  收藏  举报

导航