关于C++中的类型转换操作符

内容简介

本文对四种标准C++类型转换符: static_cast, dynamic_cast, reinterpret_cast, const_cast 进行介绍,通过本文应当能够理解这四个类型转换操作符的含义。

四种标准C++的类型转换符:

  • dynamic_cast: 动态类型转换,一般用在父类和子类指针或应用的互相转化;
  • static_cast: 静态类型转换,一般是普通数据类型转换(如 int m=static_cast(3.14));
  • reinterpret_cast: 重新解释类型转换,很像C的一般类型转换操作;
  • const_cast: 常量类型转换,是把 constvolatile 属性去掉。

下面将依次对它们进行相对详细地介绍。

static_cast

支持子类指针到父类指针的转换,并根据实际情况调整指针的值,反过来也支持,但会给出编译警告,它作用最类似C风格的“强制转换”,一般来说可认为它是安全的。

用法: static_cast < type-id > ( expression )

功能

该运算符把 expression 转换为 type-id 类型,但没有运行时类型检查来保证转换的安全性。

描述

主要如下几种用法:

  • 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换

    • 进行上行转换(把派生类的指针或引用转换成基类表示是安全的);
    • 进行下行转换(把基类指针或引用转换成派生类表示时,由于没动态类型检查,所以是不安全的)。
  • 用于基本数据类型之间的转换,如把 int 转换成 char ,把 int 转换成 enum

    这种转换的安全性也要开发人员来保证。

  • 把空指针转换成目标类型的空指针

  • 把任何类型的表达式转换成 void 类型

注意: static_cast 不能转换掉 expressionconst, volitale, 或者 __unaligned 属性。

举例

这里,关于 static_cast 的使用举例,通过与 reinterpret_cast 的例子进行对比,容易理解,所以参见后面 reinterpret_cast 的使用举例部分中对 static_cast 的使用方法。

dynamic_cast

用法: dynamic_cast < type-id > ( expression )

功能

该运算符把 expression 转换成 type-id 类型的对象, Type-id 必须是类的指针、类的引用或者 void *

描述

  • 支持子类指针到父类指针的转换,并根据实际情况调整指针的值。

    这个和 static_cast 不同,反过来它就不支持了(即不允许使用这个操作符号将父类强制转换到子类),会导致编译错误,所以这种转换是最安全的转换。

    如果 type-id 是类指针类型,那么 expression 也必须是一个指针,如果 type-id 是一个引用,那么 expression 也必须是一个引用。

注: dynamic_cast 主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。在类层次间进行上行转换时, dynamic_caststatic_cast 的效果是一样的;在进行下行转换时, dynamic_cast 具有类型检查的功能,比 static_cast 更安全。

举例

在类层次间进行转换

代码如下:

class B{
public:
      int m_iNum;
      virtual void foo();
};

class D:public B{
   public:
      char *m_szName[100];
};

void func(B *pb){
   D *pd1 = static_cast(pb);
   D *pd2 = dynamic_cast(pb);
}

这里,使用 dynamic_cast 进行转换,如果出现了把指向父类对象的指针转换成了子类的指针的时候,就会返回空值。

在上面的代码段中,如果 pb 指向一个 D 类型的对象, pd1pd2 是一样的,并且对这两个指针执行 D 类型的任何操作都是安全的;但是,如果 pb 指向的是一个 B 类型的对象,那么 pd1 将是一个指向该对象的指针,对它进行 D 类型的操作将是不安全的(如访问 m_szName ),而 pd2 将是一个空指针。(大多数面向对象编程中,经常会在代码中出现父类型的指针指向子类对象,然后在合适的时候转换为子类对象类型,直接代码中看不出指针所知对象究竟是父类还是子类,通过这个 dynamic_cast 可以检查类型,达到安全性)。

另外要注意:如果使用 dynamic_cast, B (父类)要有虚函数,否则会编译出错; static_cast 则没有这个限制。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见 )中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。

类之间的交叉转换

代码如下:

class A{
public:
       int m_iNum;
       virtual void f(){}
};

class B:public A{
};

class D:public A{
};

void foo(){
   B *pb = new B;
   pb->m_iNum = 100;
   D *pd1 = static_cast(pb);    //compile error???实践好象没有编译错误
   D *pd2 = dynamic_cast(pb); //pd2 is NULL
   delete pb;
}

这里,可见,如果出现了交叉转换的情况那么 dynamic_cast 将会返回空值。

在函数 foo 中,使用 static_cast 进行转换是不被允许的,将在编译时出错(实践中并没有报错);而使用 dynamic_cast 的转换则是允许的,结果是空指针。 保险应当使用 dynamic_cast

reinterpret_cast

用法: reinterpret_cast (expression)

功能

它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值)。 type-id 必须是一个指针、引用、算术类型、函数指针或者成员指针。

描述

reinterpret_cast 是C++里的强制类型转换符,支持任何转换,但仅仅是如它的名字所描述的“重解释”而已。也就是说:操作符修改了操作数类型,但仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换。

例如:

int *n= new int;
double *d=reinterpret_cast (n);

在进行计算以后, d 包含无用值.这是因为 reinterpret_cast 仅仅是复制 n 的比特位到 d, 没有进行必要的分析。

reinterpret_cast 是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的(C++编程思想中的原话)。将 static_castreinterpret_cast 对比一下进行解释,比较容易理解: static_cast 操作符修改了操作数类型,但是 reinterpret_cast 仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换。

例如:

int n=9;
double d=static_cast(n);

上面的例子中, 我们将一个变量从 int 转换到 double 。这些类型的二进制表达式是不同的,所以将整数9转换到双精度整数9, static_cast 需要正确地为双精度整数 d 补足比特位。其结果为 9.0

reinterpret_cast 的行为却不同:

int n=9;
double d=reinterpret_cast(n);

这里, 和 static_cast 不同,在进行计算以后, d 包含无用值。这是因为 reinterpret_cast 仅仅是复制 n 的比特位到 d, 没有进行必要的分析。

因此, 需要谨慎使用 reinterpret_cast

举例

这个例子,将 static_castreinterpret_cast 对比进行测试,具体的输出参见其中的注释。

 1 #include 
 2 using std::cout;
 3 using std::endl;
 4 class CBaseX
 5 {
 6     public:
 7         int x;
 8         CBaseX() { x = 10; }
 9         void foo() { printf("CBaseX::foo() x=%d/n", x); }
10 };
11 class CBaseY
12 {
13     public:
14         int y;
15         int* py;
16         CBaseY() { y = 20; py = &y; }
17         void bar() { printf("CBaseY::bar() y=%d, *py=%d/n", y, *py);}
18 };
19 class CDerived : public CBaseX, public CBaseY
20 {
21     public:
22         int z;
23 };
24
25 int main(int argc, char *argv[])
26 {
27     float f = 12.3;
28     float* pf = &f;
29
30     //基本类型的转换
31     cout<<"=================Basic Cast================="<的使用:
33     int n = static_cast(f); //成功编译
34     cout<<"n is :"<(pf);//编译错误,指向的类型是无关的,不能将指针指向无关的类型
36     void* pv = static_cast(pf);//编译成功
37     int* pn2 = static_cast(pv);//成功编译, 但是 *pn2是无意义的内存(rubbish)
38     cout<<"pf is:"<的使用:
42     //int i = reinterpret_cast(f);//编译错误,类型‘float’到类型‘int’的转换无效.
43     //成功编译, 但是 *pn 实际上是无意义的内存,和 *pn2一样
44     int* pi = reinterpret_cast(pf);
45     cout<<"pf is:"<(ic);//编译错误
18     int cc = const_cast(ic);
19     cout<(&ic)=200;//编译错误,从类型 ‘const int*’ 到类型 ‘int&’ 中的 const_cast 无效
21     const_cast(ic)=200;
22     cout<(pc)=200;//编译错误,从类型 ‘const int**’ 到类型 ‘int*’ 中的 const_cast 无效
27     const_cast(ic)=200;
28     //printf("%d,%d/n", ic, *pc);
29     cout<(ic);//编译错误
31     int *ppc = const_cast(&ic);
32     *ppc = 300;
33     cout<(test)= test2;
39     cout<

这里,结果输出参见每行代码相应的注释。根据结果可知:

  • 凡是对结构体或类进行这个转换,都是成功的。
  • char, short 等基本类型的转换,通过直接打印变量显示其值都是不成功的,但是通过指针却能显示出修改之后的值。

通过对代码进行反汇编,可知,虽然本身我们没使用优化,但系统还是对 ic 这个 const 进行了预编译般的替换,将它替换成 64h (十六进制的64就是十进制的100),这肯定不是一般用户想要的结果,如果它不是一个C++的规范,应该算是个C++的bug吧。

其他

注意下面一些问题。

操作对象

const_cast 操作的对象必须是 pointer, reference, nor a pointer-to-data-member type,如下代码是错误的:

const int a = 5;
int aa = const_cast(a);

而使用引用的方式,如下却是正确的:

const int a = 5;
int aa = const_cast(a);

(2)可能的误解

可能上面的描述误解的地方,根据参考资料中的一个评论,说: const_cast 只能修改变量的常引用的 const 属性,和变量的常指针的 const 属性,还有对象的 const 属性。要想改变常量本身的值是不可能的,也就是说,你改变的是引用的 const 属性,而不是常量本身的 const 属性。估计 const int ic = 100; 定义的时候就已经将这个基础类型对象放入常量符号表里面了,永远不会改变它的值。

五、其它

做为一个对前面所说的四种类型转换操作符的补充,对它们之间的区别大致进行说明一下,如下。

static_castdynamic_cast 的对比

  • static_cast 在编译期间发现错误。

    对于基本类型,它不允许将一种类型的指针指向另一种类型。所以如下代码是错误的:

    float f = 12.3;
    float* pf = &f;
    int* pn = static_cast(pf);//编译错误,指向的类型是无关的,不能将指针指向无关的类型
    

    对于复合类型(例如类、结构、联合)它允许转换子对象地址赋值给父指针,也允许转换父对象地址赋值给子指针,但是不允许两个无关的类之间的转换,所以如下是错误的:

    CBaseX *pX = &cx;
    CBaseY *pY = &cy;
    pX = static_cast(pY);//编译错误,从类型 ‘CBaseY*’ 到类型 ‘CBaseX*’ 中的 static_cast 无效。
    
  • dynamic_cast 在运行期间发生错误,它只允许它允许转换子对象地址赋值给父指针,其它情况都返回空。

    例如:

    B *pb = new B;
    D *pd = dynamic_cast(pb); //pd is NULL
    delete pb;
    

static_cast, dynamic_cast, 和 reinterpret_cast 之间的对比

转换的内容

static_castdynamic_cast 可转换指针之间或实例之间

就是说,这两个操作符可以执行指针到指针的转换,或实例本身到实例本身的转换,但不能在实例和指针之间转换。

static_cast 只能提供编译时的类型安全,而 dynamic_cast 可以提供运行时类型安全。

举个例子:

class a;class b:a;class c;

上面个类 a 是基类, b 继承 aca , b 没有关系。假设有一个函数 void function(a&a); 现在有一个对象是 b 的实例 b ,一个 c 的实例 cfunction(static_cast(b) 可以通过而 function(static(c)) 不能通过编译,因为在编译的时候编译器已经知道 ca 的类型不符,因此 static_cast 可以保证安全。

reinterpret_cast 可任意转换32bit的指针、实例

就是说,它可以转换任意一个32bit整数,包括所有的指针和整数。可以把任何整数转成指针,也可以把任何指针转成整数,以及把指针转化为任意类型的指针,威力最为强大!但不能将非32bit的实例转成指针。总之,只要是32bit的东东,怎么转都行!

对于刚刚说的例子,下面我们骗一下编译器,先把 c 转成类型 a

b& ref_b = reinterpret_castc;

这样,

  • function(static_cast(ref_b)) 就通过了!因为从编译器的角度来看,在编译时并不能知道 ref_b 实际上是 c
  • function(dynamic_cast(ref_b)) 编译时能过,在运行时却失败了,因为 dynamic_cast 在运行时检查了 ref_b 的实际类型,这样怎么也骗不过去了。

检测转换的时机

在不同时机对转换进行转换,其安全性级别也不同,一般而言,这几个运算符,若运行时检测安全性最高,编译时次之,而不检测最危险。

  • 运行时:在应用多态编程时,当我们无法确定传过来的对象的实际类型时使用 dynamic_cast
  • 编译时:如果能保证对象的实际类型,用 static_cast 就可以了,
  • 不检测:至于 reinterpret_cast 很象c语言那样的暴力转换。

总之, dynamic_cast 运行检测, static_cast 编译检测, reinterpret_cast 不检测

以上是从网上搜集的,以及根据自己的理解对C++中四种操作符号的总结,如有不准确的地方,感谢读者的告知。另外文件的图示内容由于格式转换所以不太准确,可以存成文本格式的。_

参考资料

http://zhidao.baidu.com/question/81318972.html

http://blog.csdn.net/guogangj/article/details/1545119

http://zhidao.baidu.com/question/212970514.html

http://blog.csdn.net/deyili/article/details/5354242

你可能感兴趣的:(关于C++中的类型转换操作符)