C/C++是强类型语言,不同类型之间的相互转换是比较麻烦的.但是在编程实践中,不可避免的要用到类型转换.有2种类型转换:隐式类型转换和强制类型转换.

隐式类型转换

  1. 提升精度,此种是编译器自动完成的,安全的.所以编译的时候不会有任何错误或者警告信息提示.

     short a=2000;
     int b;
     b=a;
    

    short是两字节,int是四字节,由short型转成int型是宽化转换(bit位数增多),编译器没有warning.

    宽化转换(如char到int,int到long long,int到float,float到double,int到double等)构成隐式转换,编译器允许直接转换。

  2. 降低精度,也是有编译器自动完成,会造成精度丢失,所以编译时得到一个警告信息提示.

     double a=2000;	
     short b;
     b=a;
    

    此时,是从8字节的double型转成2字节的short型变量,是窄化转换,编译器就会有warning了,提醒程序员可能丢失数据。不过需要注意的是,有些隐式转换,编译器可能并不给出warning,比如int到short,但数据溢出却依然会发生。

  3. 多态的上行转换。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    #include <iostream>
    using namespace std;
    class A
    {
         // ......
    };
    class B : public A
    {
         // ......
    };
    int main()
    {
         B *pB = new B;
         A *pA = pB; // Safe and will succeed
    }
    

    ``

  4. 任意类型指针到 void*, 都可以使用隐式类型转换,因为void*是通用指针。

  5. 非const对象转化为const对象,直接进行隐式转换。

注意:普通变量与指针不能隐式转换转换,比如int*和int不能发生隐式转换.

隐式转换失败后会出现编译错误.

显式类型转换

显式类型转换可以进行任何转换。

C风格的强制转换(包括旧式C++风格的强制转换)

格式:

类型(表达式); // 旧的C++风格
或者 
(类型)表达式 // C风格

示例: int(dval) 或者 (int)dval

此种强制转换是比较粗暴直接的,有可能导致精度丢失(如从 double 转换为 int)或者一些莫名其妙的错误(如把 int 转换为 函数指针),一旦使用了强制转换,编译器将不提示任何警告.这也往往成为错误的源泉.而且这种错误非常难找.

C++强制转换操作符

C++增加了4个关键字用于强制类型转换:

static_cast, reinterpret_cast, const_cast 和 dynamic_cast.

static_cast(静态转换)

static_cast的转换格式:static_cast (expression)

将expression转换为type-id类型,比起强制类型转换提供了一定的安全性。在无关类的类指针之间转换上进行限制。主要用于非多态类型之间的转换,不提供运行时的检查来确保转换的安全性。

静态转换的功能如下:

  1. 用于相关数据类型之间的转换,如基本数据类型中把int转换成char,把int转换成enum等等

    数值精度提高或者降低,包括把无符号型转换为带符号型(也是精度损失的一种),用 static_cast 可以消除编译器的警告信息。

  2. 静态转换能把void*指针转换成目标类型的指针,因为void*是通用指针,和其它指针有一定关系。

  3. 用于类层次结构中,基类和子类之间指针和引用的转换。但不推荐使用!

    当进行上行转换,也就是把子类的指针或引用转换成父类表示,这种转换是安全的;

    当进行下行转换,也就是把父类的指针或引用转换成子类表示,这种转换是不安全的,但编译可以通过,需要程序员来保证安全性;

注:static_cast不能转换掉expression的const、volatile和__unaligned属性。

dynamic_cast(动态转换)

dynamic_cast的转换格式:dynamic_cast (expression)

动态转换确保类指针的转换是合适完整的,它有两个重要的约束条件:

  1. 要求type为指针或引用

    将expression转换为type-id类型,type-id必须是类的指针、类的引用或者是void *;如果type-id是指针类型,那么expression也必须是一个指针;如果type-id是一个引用,那么expression也必须是一个引用。

  2. 下行转换时要求基类是多态的(基类中包含至少一个虚函数)。

动态转换的功能如下:

  1. 在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

  2. 在多重继承中的上行转换中,dynamic_cast可以实现同层基类的互相转换。

  3. 在类层次间进行上行转换时,隐式转换和dynamic_cast和static_cast的效果是一样的,不推荐使用!

  4. 将类的指针转换成void *指针,注意这也需要保证类A和B都有虚函数,不推荐使用!

下面我将分别在以下的几种场合下进行dynamic_cast的使用总结:

  1. 类之间的下行转换。

    如果expression是type-id的基类,使用dynamic_cast进行转换时,在运行时就会检查expression是否真正的指向一个type-id类型的对象,如果是,则能进行正确的转换,获得对应的值;否则返回NULL,如果是引用,则在运行时就会抛出异常;例如:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    class B
    {
         virtual void f(){};
    };
    class D : public B
    {
         virtual void f(){};
    };
    void main()
    {
         B* pb = new D;   // unclear but ok
         B* pb2 = new B;
         D* pd = dynamic_cast<D*>(pb);   // ok: pb actually points to a D
         D* pd2 = dynamic_cast<D*>(pb2);   // pb2 points to a B not a D, now pd2 is NULL
    }
    

    ``

    但是,在类B中必须包含虚函数,为什么呢?因为类中存在虚函数,就说明它有想让基类指针或引用指向派生类对象的情况,此时转换才有意义;由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表。

  2. 可以将类的指针转换成void *指针,注意这也需要保证类A和B都有虚函数。例如:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    class A
    {	
    public:
         virtual void f(){}
         // ......
    };
    class B
    {
    public:
         virtual void f(){}
         // ......
    };
    int main()
    {
         A *pA = new A;
         B *pB = new B;
         void *pV = dynamic_cast<void *>(pA); // pV points to an object of A
         pV = dynamic_cast<void *>(pB); // pV points to an object of B
    }
    

    ``

  3. dynamic_cast可以实现同层基类的互相转换,这是static_cast无法实现的。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    class   Base   
    {
    public:
        virtual ~Base(){} //donnt forget : at least one virtual function.....
    };   
    class   A   :   public   Base   {};   
    class   B   :   public   Base   {};   
    class   AB   :   public   A,   public   B   {};   
    int     main()   
    {   
        AB   ab;   
        B   *b   =   &ab;   
        A * a = dynamic_cast<A*>(b);
        return   0;   
    }
    

    ``

对于一些复杂的继承关系来说,使用dynamic_cast进行转换是存在一些陷阱的。

讨论上行转换,(此处所有动态转换都可以用隐式转换替代)有如下的一个结构:

D类型可以安全的转换成B和C类型,但是D类型要是直接转换成A类型呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class A
{
     virtual void Func() = 0;
};
class B : public A
{
     void Func(){};
};
class C : public A
{
     void Func(){};
};
class D : public B, public C
{
     void Func(){}
};
int main()
{
     D *pD = new D;
     A *pA = dynamic_cast<A *>(pD); // You will get a pA which is NULL
}

如果进行上面的直接转,你将会得到一个NULL的pA指针;这是因为,B和C都继承了A,并且都实现了虚函数Func,导致在进行转换时,无法进行抉择应该向哪个A进行转换。正确的做法是:

1
2
3
4
5
6
int main()
{
     D *pD = new D;
     B *pB = dynamic_cast<B *>(pD);
     A *pA = dynamic_cast<A *>(pB);
}

讨论下行转换,有如下结构:

现在,你拥有一个A类型的指针,它指向E实例,如何获得B类型的指针,指向E实例呢?

如果直接进行转的话,就会出现编译器出现分歧,不知道是走E->C->B,还是走E->D->B。对于这种情况,我们就必须先将A类型的指针选择正确的路线进行下行转换,获得E类型的指针,然后,在指定一条正确的路线进行上行转换。

reinterpret_cast

reinterpret_cast的转换格式:reinterpret_cast (expression)

reinterpret_cast 常常被用作不同类型指针间的相互转换。

static_cast 运算符完成相关类型之间的转换. 而 reinterpret_cast 处理互不相关的类型之间的转换.互不相关的类型”指的是两种完全不同的类型,如从整型到指针类型,或者从一个指针到另一个毫不相干的指针.

对于static_cast操作符,如果需要截断,补齐或者指针偏移编译器都会自动完成.而对于reinterpret_cast,编译器不会做任何检查,截断,补齐的操作,只是把比特位拷贝过去.

示例:

int ival = 1;
double *dptr = reinterpret_cast<double*>(ival);

或者

int *iptr = NULL;
double *dptr = reinterpret_cast<double*>(iptr);

上面这个示例也说明了 reinterpret_cast 的意思:.

reinterpret_cast的功能如下:

  1. 不同类型指针间的相互转换,因为所有类型的指针的长度都是一致的(32位系统上都是4字节),按比特位拷贝后不会损失数据.

  2. void 到任意类型指针的转换, 用 static_cast 和 reinterpret_cast 都可以,这是由 void 是通用指针这个语义决定的.

  3. 它还可用于将一种数据类型按比特位从一种类型转换为另一种类型。常用的是int 型和指针类型间的相互转换,但是这种转换在系统底层的操作,有极强的平台依赖性,移植性不好。

注意:考虑到C++对象模型的内存分布可能引起的指针偏移问题,绝对不能在多态情况下用 reinterpret_cast.

写代码的时候经常这样做: new 一个 struct,然后把指针返回给外部函数作为一个”句柄”,我不希望外部函数知道这是一个指针,只需要外部函数在调用相关函数时把这个”句柄”重新传回来.这时,就可以把指针转换为一个 int 型返回. 这是 reinterpret_cast 存在的绝佳理由.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct car
{
    int doors;
    int height;
    int length;
    float weight; 
};
int create_car()
{
    car *c = new car;
    return reinterpret_cast<int>(c);
}
int get_car_doors(int car_id)
{
    car *c = reinterpret_cast<car*>(car_id);
    return c->doors;
}
void destroy_car(int car_id)
{
    car *c = reinterpret_cast<car*>(car_id);
    delete c;
}

const_cast

const_cast的转换格式:const_cast (expression)

该运算符用来修改类型的const属性。.有以下功能:

  1. 常量指针和非常量指针的相互转换,并且仍然指向原来的对象;要求type—id和expression都为指针

  2. 常量引用和非常量引用的相互转换,并且仍然指向原来的对象; 要求type-id为引用,expression可以是指向对象的引用,也可以是对象本身的名字。

  3. 常量对象和非常量对象的相互转换,两者之间毫无关系。要求type-id为引用。

type_id 必须为指针或引用,const_cast转换符不该用在对象数据上,因为这样的转换得到的两个变量/对象并没有相关性。只有用指针或者引用,让变量指向同一个地址才是解决方案.

const_cast通常用于常量转换为非常量,因为非常量转换为常量可以直接用隐式转换完成。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int main(int argc, char ** argv_)   
{  
	 int i = 100;  
	 int *j = &i;  
	 //const int *k = const_cast<const int*>(j);  
 	 const int *m = j; //使用隐式转换即可。  
 	 //指的地址都一样  
	 cout <<i<<","<<&i<<endl; //100, 0012FF78  
	 cout <<*j<<","<<j<<endl; //100, 0012FF78  
     *j = 200;  
     return 0;  
}

下面给出一些例子来进行解析,类B的结构如下:

1
2
3
4
5
6
7
class B  
{  
    public:  
    int m_iNum;  
    B() : m_iNum(50)  
    {   }  
}; 
  1. 指针的转换

    1
    2
    3
    4
    5
    
    const B *b1 = new B();  
    B *b2 = const_cast<B*>(b1);//指针之间的转换,两者指向同一地址
    b2->m_iNum = 200;  
    cout<<"b1: "<< b1->m_iNum <<endl;  //200
    cout<<"b2: "<< b2->m_iNum <<endl;  //200
    

    ``

  2. 引用的转换

    1
    2
    3
    4
    5
    6
    
    const B b5;  
    // 左侧一定要用引用类型,否则b6和b5无关,不指向同一地址。
    B &b6 = const_cast<B&>(b5);  
    b6.m_iNum = 200;  
    cout<<"b5: "<<b5.m_iNum <<endl;  
    cout<<"b6: "<<b6.m_iNum <<endl;
    
  3. 对象的转换

    1
    2
    3
    4
    5
    
    const B b3;   
    B b4 = const_cast<B&>(b3);          //注意type-id是引用类型
    b4.m_iNum = 200;  
    cout<<"b3: "<<b3.m_iNum <<endl;	//50   
    cout<<"b4: "<<b4.m_iNum <<endl; //200
    

    ``

const_cast与编译器优化

因为编译器的优化,会出现取消了const属性的const变量却不能修改其值的情况。

1
2
3
4
5
const int xx = 50;  
int* yy = const_cast<int *> (&xx);     // 同样的地址,但是内容是不一样的  
*yy = 200;  
cout << "xx: "<<xx<<" address: "<<&xx<<endl;  //50
cout << "*yy: "<<*yy<<" address: "<<yy<<endl; //200

究其原因,在于const值初始化的内容:如果是以字面值初始化的话,编译器在编译的时候就把const的值用字面值取代了,而不是从const对应的地址进行取值。

解决方案就是不让const以字面值初始化,如下:

1
2
3
4
5
6
int xxx = 50;//注意不能是const,否则xx仍是以字面值初始化。
const int xx = xxx;//将xxx隐式转换为const,然后初始化。  
int* yy = const_cast<int *> (&xx);     // 同样的地址,内容也相同。
*yy = 200;  
cout << "xx: "<<xx<<" address: "<<&xx<<endl;  //50
cout << "*yy: "<<*yy<<" address: "<<yy<<endl; //200

还有一种方法就是使用valatile关键字。

vs(Release)和g++都会执行编译器优化。