类型转化也许大家并不陌生,int i; float j; j = (float)i; i = (int)j; 像这样的显式转化其实很常见,强制类型转换可能会丢失部分数据,所以如果不加(int)做强制转换,严检查的编译会报错,宽检查的编译会报warning。在C语言中,指针是4字节或者8字节的,所以指针之间的强制转换在转换的时候就如同不同的整数类型之间的赋值,问题在于对该指针的使用上,必须确保该指针确实可以做出这样的强制转换。常见的情况是void到不同的指针类型(比如内存分配,参数传递),char和unsigned char*这样的转换。也有在读文件的时候,直接把某个结构映射为内存,写文件的时候,把某块内存直接映射成结构体。但其实在C++中,有用于专门用于显示类型转化的更合适更安全的语法。
主要包括四种:static_cast、const_cast、reinterpret_cast、dynamic_cast。四种转化的用途各不相同,下面一一介绍:
语法:A = static_cast<typeA>(B)
把B显式转化为typeA类型,static_cast是最常用到的转化操作符,使用它可以消除因产生类型转化而可能产生的编译器warnings,static_cast全部用于明确定义的变换,包括编译器允许我们做的不用强制转换的“安全”变换和不太安全但清楚定义的变换。static_cast包含的转化类型包括典型的非强制类型转换、窄化变化(会有信息丢失)、使用void*的强制变换、隐式类型变换和类层次的静态定位(基类和派生类之间的转换)。
说明代码如下:
1 #include <iostream>
2 using namespace std;
3
4 void func(int){}
5
6 int main(){
7 int i = 0x7fff;
8 long l;
9 float f;
10 //情况1,向宽数据转化
11 l = i;
12 f = i;
13 //此时同样可以用static_cast
14 l = static_cast<long>(i);
15 f = static_cast<float>(i);
16
17 cout << "l = " << l << endl;
18 cout << "f = " << f << endl;
19
20 //情况2,向窄数据转化,可能发生精度丢失问题
21 i = l;
22 i = f;
23 cout << "i = " << i << endl;
24 //此时使用static_cast,类似于告诉编译器我清楚这种事情的发生,不用担心,也能消除警告
25 i = static_cast<int>(l);
26 i = static_cast<int>(f);
27 char c = static_cast<char>(i);
28 cout << "c = " << c << endl;
29
30 //情况3,将void*类型强制转换为其他类型
31 void * vp = &i;
32 float* fp = (float*) vp;//这是一个危险的转换
33 fp = static_cast<float*>(vp);//这样同样危险
34
35 //情况4,隐式类型转换
36 double d = 0.0;
37 int x = d;//自动类型转化
38 x = static_cast<int>(d);//这样声明更加明显
39 func(d);//自动类型转化
40 func(static_cast<int>(d));//这样声明更加明显
41 }
更重要的应用是在于基类与派生类之间的转换
class Base{};
class derv:public Base{};
derv dd;
Base bb = static_cast(dd);//具有继承关系的类型之间转换
Base *pb = new Base;
derv *pd = static_cast(pb);//基类转继承类
derv* pd1 = new derv;
Base* pb1 = static_cast(pd1);//继承类指针转父类指针
语法:A = const_cast<typeA>(B)
这个运算符可以用来去除一个对象的const或volatile属性。typeA必须是一个指针或者引用。
1 #include <iostream>
2 using namespace std;
3 int main(){
4 const int i = 0;
5 int* j = (int*)&i;//不推荐使用的方法
6 j = const_cast<int*>(&i);
7 cout << *j << endl;
8 *j = 10;
9 cout << *j << " " << i << endl;
10 }
语法:A = reinterpret_cast<typeA>(B)
这是一种最不安全的转换,最有可能出现问题,reinterpret_cast把对象假想为模式,仿佛它是一个完全不同类型的对象,这是低级的位操作,修改了操作数类型,但仅仅重新解释了对象的比特模型而没有进行二进制转换,在使用reinterpret_cast做任何事情之前,实际上总是需要它回到原来的类型。
从语法上看,这个操作符仅用于指针类型的转换(返回值是指针)。它用来将一个类型指针转换为另一个类型指针,它只需在编译时重新解释指针的类型。
这个操作符基本不考虑转换类型之间是否是相关的。
reinterpret_cast的本质(http://blog.csdn.net/coding_hello/archive/2008/03/24/2211466.aspx) 一文做的试验很好的解释了reinterpret_cast不做二进制转换的特点。我喜欢从C语言的角度来理解这个操作符,就像C语言中的指针强制转换,其实只是把地址赋给了新的指针,其它的不做改变,只在新的指针使用的时候,进行不一样的解释。看如下的例子:
1 #include <iostream>
2 using namespace std;
3 const int sz = 100;
4
5 struct X {
6 int a[sz];
7 };
8
9 void print(X* x){
10 for(int i = 0; i < sz; i++)
11 cout << x->a[i] << ' ';
12 cout << endl << "-------------------------" << endl;
13 }
14
15 int main(){
16 X x;
17 print(&x);//输出尚未初始化的结构体内数组
18 int* xp = reinterpret_cast<int*>(&x);//重解释转换,取得x的地址并转换成一个整数指针
19 for(int* i = xp; i < xp + sz; i++)//然后用该指针遍历这个数组,置每个整数元素为0
20 *i = 0;
21 print(reinterpret_cast<X*>(xp));
22 print(&x);
23 }
reinterpret_cast的思想就是当需要使用的时候,得到的东西已经转换成不同的类型了,以至于它不能用于类型原来的目的,除非再次把它转换回来。这里打印调用中转换回X。xp只有作为int才有用,这是对原来的X的重新解释。使用renterpret_cast通常不是一个明智的做法,但是当需要用到的时候,它是十分有用的。
reinterpret_cast常用的场景如下:
1)普通指针转换,T—>U—>T,保证T经过一些列转换值不变
比如将不同类型的指针存在一个容器里,vector可以存int,char,string等各种指针,只要有别的方式确定某个void当初的类型是T*,标准保证reinterpret_cast(v[i])可以得到当初的值。
2)自己做memory allocator,可以将T转换为U,这个时候可能要注意字节对其的问题。
语法:A=dynamic_cast<typeA>(B)
该运算符把B转换成typeA类型的对象。TypeA必须是类的指针、类的引用或者void *;
dynamic_cast的转换是在运行时进行的,它的一个好处是会在运行是做类型检查,如果对象的类型不是期望的类型,它会在指针转换的时候返回NULL,并在引用转换的时候抛出一个std::bad_cast异常。
dynamic_cast一般只在继承类对象的指针之间或引用之间进行类型转换。如果没有继承关系,则被转化的类具有虚函数对象的指针进行转换。
1 struct A {
2 virtual void f() { }
3 };
4 struct B : public A { };
5 struct C { };
6
7 void f () {
8 A a;
9 B b;
10
11 A* ap = &b;
12 B* b1 = dynamic_cast (&a); // NULL, because 'a' is not a 'B'
13 B* b2 = dynamic_cast (ap); // 'b'
14 C* c = dynamic_cast (ap); // NULL.
15
16 A& ar = dynamic_cast (*ap); // Ok.
17 B& br = dynamic_cast (*ap); // Ok.
18 C& cr = dynamic_cast (*ap); // std::bad_cast
19 }
真正重要的东西,用眼睛是看不见的。