内容简介
本文对四种标准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
: 常量类型转换,是把const
或volatile
属性去掉。
下面将依次对它们进行相对详细地介绍。
static_cast
支持子类指针到父类指针的转换,并根据实际情况调整指针的值,反过来也支持,但会给出编译警告,它作用最类似C风格的“强制转换”,一般来说可认为它是安全的。
用法: static_cast < type-id > ( expression )
功能
该运算符把 expression
转换为 type-id
类型,但没有运行时类型检查来保证转换的安全性。
描述
主要如下几种用法:
-
用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换
- 进行上行转换(把派生类的指针或引用转换成基类表示是安全的);
- 进行下行转换(把基类指针或引用转换成派生类表示时,由于没动态类型检查,所以是不安全的)。
-
用于基本数据类型之间的转换,如把
int
转换成char
,把int
转换成enum
这种转换的安全性也要开发人员来保证。
把空指针转换成目标类型的空指针
把任何类型的表达式转换成
void
类型
注意: static_cast
不能转换掉 expression
的 const
, 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_cast
和 static_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
类型的对象, pd1
和 pd2
是一样的,并且对这两个指针执行 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
功能
它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值)。 type-id
必须是一个指针、引用、算术类型、函数指针或者成员指针。
描述
reinterpret_cast
是C++里的强制类型转换符,支持任何转换,但仅仅是如它的名字所描述的“重解释”而已。也就是说:操作符修改了操作数类型,但仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换。
例如:
int *n= new int;
double *d=reinterpret_cast (n);
在进行计算以后, d
包含无用值.这是因为 reinterpret_cast
仅仅是复制 n
的比特位到 d
, 没有进行必要的分析。
reinterpret_cast
是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的(C++编程思想中的原话)。将 static_cast
和 reinterpret_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_cast
和 reinterpret_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:"<的使用:
73 CBaseY* pY2 = reinterpret_cast(pD);// 成功编译, 但是 pY2 不是 CBaseY*
74 cout<<"CDerived* pD = "<(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_cast
和 dynamic_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_cast
和 dynamic_cast
可转换指针之间或实例之间
就是说,这两个操作符可以执行指针到指针的转换,或实例本身到实例本身的转换,但不能在实例和指针之间转换。
static_cast
只能提供编译时的类型安全,而 dynamic_cast
可以提供运行时类型安全。
举个例子:
class a;class b:a;class c;
上面个类 a
是基类, b
继承 a
, c
和 a
, b
没有关系。假设有一个函数 void function(a&a);
现在有一个对象是 b
的实例 b
,一个 c
的实例 c
。 function(static_cast(b)
可以通过而 function(static(c))
不能通过编译,因为在编译的时候编译器已经知道 c
和 a
的类型不符,因此 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