(使用vs2010所带的编译器) 转载请注明来源 http://www.cnblogs.com/jerry19880126/
隐式转换(implicit conversion)
short a=2000;
int b;
b=a;
short是两字节,int是四字节,由short型转成int型是宽化转换(bit位数增多),编译器没有warning,如下图所示。宽化转换(如char到int,int到long long,int到float,float到double,int到double等)构成隐式转换,编译器允许直接转换。
但若反过来
double a=2000;
short b;
b=a;
此时,是从8字节的double型转成2字节的short型变量,是窄化转换,编译器就会有warning了,如下所示,提醒程序员可能丢失数据。不过需要注意的是,有些隐式转换,编译器可能并不给出warning,比如int到short,但数据溢出却依然会发生。
C风格显式转换(C style explicit conversion)
要去掉上述waring很简单,熟悉C语言的程序员知道,有两种简单的写法(C风格转换与函数风格转换):
double a=2000.3;
short b;
b = (short) a; // c-like cast notation
b = short (a); // functional notation
如下图所示,此时warning就没了
这种显式转换方式简单直观,但并不安全,举一个父类和子类的例子如下:
1 // class type-casting 2 #include <iostream> 3 using namespace std; 4 5 class CDummy { 6 float i,j; 7 CDummy():i(100),j(10){} 8 }; 9 10 class CAddition:public CDummy 11 { 12 int *x,y; 13 public: 14 CAddition (int a, int b) { x=&a; y=b; } 15 int result() { return *x+y;} 16 }; 17 18 int main () { 19 CDummy d; 20 CAddition * padd; 21 padd = (CAddition*) &d; 22 cout << padd->result(); 23 return 0; 24 }
编译器不报任何错,但运行结果出错,如下图所示:
究其原因,注意这一句:
padd = (CAddition*) &d;
此时父类的指针&d被C风格转换方式强制转成了子类的指针了,后面调用了子类的方法result,需要访问*x,但指针指向的对象本质还是父类的,所以x相当于父类中的i,y相当于父类中的j,*x相当于*i,但i是float型变量(初始化为100),不是地址,所以出错,如果程序员正是鲁莽地对这个地址指向的内存进行写入操作,那将可能会破坏系统程序,导致操作系统崩溃!
这里有一个重要概念,CAddition*是子类的指针,它的变量padd可以调用子类的方法,但是它指向的是父类的对象,也就是说padd指向的内存空间里存放的是父类的成员变量。深入地说,数据在内存中是没有“类型”一说的,比如0x3F可能是字符型,也可能是整型的一部分,还可能是地址的一部分。我们定义的变量类型,其实就是定义了数据应该“被看成什么”的方式。
因此padd类指针实质是定义了取值的方式,如padd->x就是一并取出内存空间里的0号单元至3号单元的值(共4个字节),将其拼成32位并当作指针,padd->y则取出内存空间里的4号单元至7号单元(共4个字节),将其拼成32位并当作int型变量。但实际上padd指向的是父类的对象,也就是前4个字节是float型变量,后4个字节也是float型变量。
从这里可以看出,程序员的这种转换使编译器“理解”出错,把牛当成马了。
从上可见,用C风格的转换其实是不安全的,编译器无法看到转换的不安全。
上行转换(up-casting)与下行转换(down-casting)
看到这个,读者可能会问,哪些转换不安全?根据前面所举的例子,可以看到,不安全来源于两个方面:其一是类型的窄化转化,会导致数据位数的丢失;其二是在类继承链中,将父类对象的地址(指针)强制转化成子类的地址(指针),这就是所谓的下行转换。“下”表示沿着继承链向下走(向子类的方向走)。
类似地,上行转换的“上”表示沿继承链向上走(向父类的方向走)。
我们给出结论,上行转换一般是安全的,下行转换很可能是不安全的。
为什么呢?因为子类中包含父类,所以上行转换(只能调用父类的方法,引用父类的成员变量)一般是安全的。但父类中却没有子类的任何信息,而下行转换会调用到子类的方法、引用子类的成员变量,这些父类都没有,所以很容易“指鹿为马”或者干脆指向不存在的内存空间。
值得一说的是,不安全的转换不一定会导致程序出错,比如一些窄化转换在很多场合都会被频繁地使用,前提是程序员足够小心以防止数据溢出;下行转换关键看其“本质”是什么,比如一个父类指针指向子类,再将这个父类指针转成子类指针,这种下行转换就不会有问题。
针对类指针的问题,C++特别设计了更加细致的转换方法,分别有:
static_cast <new_type> (expression)
dynamic_cast <new_type> (expression)
reinterpret_cast <new_type> (expression)
const_cast <new_type> (expression)
可以提升转换的安全性。
static_cast <new_type> (expression) 静态转换
静态转换是最接近于C风格转换,很多时候都需要程序员自身去判断转换是否安全。比如:
double d=3.14159265;
int i = static_cast<int>(d);
但static_cast已经有安全性的考虑了,比如对于不相关类指针之间的转换。参见下面的例子:
1 // class type-casting 2 #include <iostream> 3 using namespace std; 4 5 class CDummy { 6 float i,j; 7 }; 8 9 class CAddition { 10 int x,y; 11 public: 12 CAddition (int a, int b) { x=a; y=b; } 13 int result() { return x+y;} 14 }; 15 16 int main () { 17 CDummy d; 18 CAddition * padd; 19 padd = (CAddition*) &d; 20 cout << padd->result(); 21 return 0; 22 }
这个例子与之前举的例子很像,只是CAddition与CDummy类没有任何关系了,但main()中C风格的转换仍是允许的padd = (CAddition*) &d,这样的转换没有安全性可言。
如果在main()中使用static_cast,像这样:
1 int main () { 2 CDummy d; 3 CAddition * padd; 4 padd = static_cast<CAddition*> (&d); 5 cout << padd->result(); 6 return 0; 7 }
编译器就能看到这种不相关类指针转换的不安全,报出如下图所示的错误:
注意这时不是以warning形式给出的,而直接是不可通过编译的error。从提示信息里可以看到,编译器说如果需要这种强制转换,要使用reinterpret_cast(稍候会说)或者C风格的两种转换。
总结一下:static_cast最接近于C风格转换了,但在无关类的类指针之间转换上,有安全性的提升。
dynamic_cast <new_type> (expression) 动态转换
动态转换确保类指针的转换是合适完整的,它有两个重要的约束条件,其一是要求new_type为指针或引用,其二是下行转换时要求基类是多态的(基类中包含至少一个虚函数)。
看一下下面的例子:
1 #include <iostream> 2 using namespace std; 3 class CBase { }; 4 class CDerived: public CBase { }; 5 6 int main() 7 { 8 CBase b; CBase* pb; 9 CDerived d; CDerived* pd; 10 11 pb = dynamic_cast<CBase*>(&d); // ok: derived-to-base 12 pd = dynamic_cast<CDerived*>(&b); // wrong: base-to-derived 13 }
在最后一行代码有问题,编译器给的错误提示如下图所示:
把类的定义改成:
class CBase { virtual void dummy() {} };
class CDerived: public CBase {};
再编译,结果如下图所示:
编译都可以顺利通过了。这里我们在main函数的最后添加两句话:
cout << pb << endl;
cout << pd << endl;
输出pb和pd的指针值,结果如下:
我们看到一个奇怪的现象,将父类经过dynamic_cast转成子类的指针竟然是空指针!这正是dynamic_cast提升安全性的功能,dynamic_cast可以识别出不安全的下行转换,但并不抛出异常,而是将转换的结果设置成null(空指针)。
再举一个例子:
1 #include <iostream> 2 #include <exception> 3 using namespace std; 4 5 class CBase { virtual void dummy() {} }; 6 class CDerived: public CBase { int a; }; 7 8 int main () { 9 try { 10 CBase * pba = new CDerived; 11 CBase * pbb = new CBase; 12 CDerived * pd; 13 14 pd = dynamic_cast<CDerived*>(pba); 15 if (pd==0) cout << "Null pointer on first type-cast" << endl; 16 17 pd = dynamic_cast<CDerived*>(pbb); 18 if (pd==0) cout << "Null pointer on second type-cast" << endl; 19 20 } catch (exception& e) {cout << "Exception: " << e.what();} 21 return 0; 22 }
输出结果是:Null pointer on second type-cast
两个dynamic_cast都是下行转换,第一个转换是安全的,因为指向对象的本质是子类,转换的结果使子类指针指向子类,天经地义;第二个转换是不安全的,因为指向对象的本质是父类,“指鹿为马”或指向不存在的空间很可能发生!
最后补充一个特殊情况,当待转换指针是void*或者转换目标指针是void*时,dynamic_cast总是认为是安全的,举个例子:
1 #include <iostream> 2 using namespace std; 3 class A {virtual void f(){}}; 4 class B {virtual void f(){}}; 5 6 int main() { 7 A* pa = new A; 8 B* pb = new B; 9 void* pv = dynamic_cast<void*>(pa); 10 cout << pv << endl; 11 // pv now points to an object of type A 12 13 pv = dynamic_cast<void*>(pb); 14 cout << pv << endl; 15 // pv now points to an object of type B 16 }
运行结果如下:
可见dynamic_cast认为空指针的转换安全的,但这里类A和类B必须是多态的,包含虚函数,若不是,则会编译报错。
reinterpret_cast <new_type> (expression) 重解释转换
这个转换是最“不安全”的,两个没有任何关系的类指针之间转换都可以用这个转换实现,举个例子:
class A {};
class B {};
A * a = new A;
B * b = reinterpret_cast<B*>(a);//correct!
更厉害的是,reinterpret_cast可以把整型数转换成地址(指针),这种转换在系统底层的操作,有极强的平台依赖性,移植性不好。
它同样要求new_type是指针或引用,下面的例子是通不过编译的:
double a=2000.3;
short b;
b = reinterpret_cast<short> (a); //compile error!
const_cast <new_type> (expression) 常量向非常量转换
这个转换好理解,可以将常量转成非常量。
1 // const_cast 2 #include <iostream> 3 using namespace std; 4 5 void print (char * str) 6 { 7 cout << str << endl; 8 } 9 10 int main () { 11 const char * c = "sample text"; 12 char *cc = const_cast<char *> (c) ; 13 Print(cc); 14 return 0; 15 }
从char *cc = const_cast<char *>(c)可以看出了这个转换的作用了,但切记,这个转换并不转换原常量本身,即c还是常量,只是它返回的结果cc是非常量了。
总结
C风格转换是“万能的转换”,但需要程序员把握转换的安全性,编译器无能为力;static_cast最接近于C风格转换,但在无关类指针转换时,编译器会报错,提升了安全性;dynamic_cast要求转换类型必须是指针或引用,且在下行转换时要求基类是多态的,如果发现下行转换不安全,dynamic_cast返回一个null指针,dynamic_cast总是认为void*之间的转换是安全的;reinterpret_cast可以对无关类指针进行转换,甚至可以直接将整型值转成指针,这种转换是底层的,有较强的平台依赖性,可移植性差;const_cast可以将常量转成非常量,但不会破坏原常量的const属性,只是返回一个去掉const的变量。
注:本文中大部分样例来源自C++标准网站:
http://www.cplusplus.com/doc/tutorial/typecasting/
以及微软的MSDN:
http://msdn.microsoft.com/en-us/library/cby9kycs
若有理解出错的地方,望不吝指正。
- static_cast和reinterpret_cast揭秘 收藏
- 本文讨论static_cast<> 和 reinterpret_cast<>。
- reinterpret_cast可以转换任意一个32bit整数,包括所有的指针和整数。可以把任何整数转成指针,也可以把任何指针转成整数,以及把指针转化为任意类型的指针,威力最为强大!但不能将非32bit的实例转成指针。总之,只要是32bit的东东,怎么转都行!
- static_cast和dynamic_cast可以执行指针到指针的转换,或实例本身到实例本身的转换,但不能在实例和指针之间转换。static_cast只能提供编译时的类型安全,而dynamic_cast可以提供运行时类型安全。举个例子:
- class a;class b:a;class c。
- 上面三个类a是基类,b继承a,c和ab没有关系。
- 有一个函数void function(a&a);
- 现在有一个对象是b的实例b,一个c的实例c。
- function(static_cast<a&>(b)可以通过而function(static<a&>(c))不能通过编译,因为在编译的时候编译器已经知道c和a的类型不符,因此static_cast可以保证安全。
- 下面我们骗一下编译器,先把c转成类型a
- b& ref_b = reinterpret_cast<b&>c;
- 然后function(static_cast<a&>(ref_b))就通过了!因为从编译器的角度来看,在编译时并不能知道ref_b实际上是c!
- 而function(dynamic_cast<a&>(ref_b))编译时也能过,但在运行时就失败了,因为dynamic_cast在运行时检查了ref_b的实际类型,这样怎么也骗不过去了。
- 在应用多态编程时,当我们无法确定传过来的对象的实际类型时使用dynamic_cast,如果能保证对象的实际类型,用static_cast就可以了。至于reinterpret_cast,我很喜欢,很象c语言那样的暴力转换:)
- dynamic_cast:动态类型转换
- static_cast:静态类型转换
- reinterpret_cast:重新解释类型转换
- const_cast:常量类型转换
- 专业的上面很多了,我说说我自己的理解吧:
- synamic_cast一般用在父类和子类指针或应用的互相转化;
- static_cast一般是普通数据类型(如int m=static_cast<int>(3.14));
- reinterpret_cast很像c的一般类型转换操作
- const_cast是把cosnt或volatile属性去掉
- .
- 介绍
- 大多程序员在学C++前都学过C,并且习惯于C风格(类型)转换。当写C++(程序)时,有时候我们在使用static_cast<>和reinterpret_cast<>时可能会有点模糊。在本文中,我将说明static_cast<>实际上做了什么,并且指出一些将会导致错误的情况。
- 泛型(Generic Types)
- float f = 12.3;
- float* pf = &f;
- // static cast<>
- // 成功编译, n = 12
- int n = static_cast<int>(f);
- // 错误,指向的类型是无关的(译注:即指针变量pf是float类型,现在要被转换为int类型) //int* pn = static_cast<int*>(pf);
- //成功编译
- void* pv = static_cast<void*>(pf);
- //成功编译, 但是 *pn2是无意义的内存(rubbish)
- int* pn2 = static_cast<int*>(pv);
- // reinterpret_cast<>
- //错误,编译器知道你应该调用static_cast<>
- //int i = reinterpret_cast<int>(f);
- //成功编译, 但是 *pn 实际上是无意义的内存,和 *pn2一样
- int* pi = reinterpret_cast<int*>(pf);简而言之,static_cast<> 将尝试转换,举例来说,如float-到-integer,而reinterpret_cast<>简单改变编译器的意图重新考虑那个对象作为另一类型。
- 指针类型(Pointer Types)
- 指针转换有点复杂,我们将在本文的剩余部分使用下面的类:
- class CBaseX
- {
- public:
- int x;
- CBaseX() { x = 10; }
- void foo() { printf("CBaseX::foo() x=%d/n", x); }
- };
- class CBaseY
- {
- public:
- int y;
- int* py;
- CBaseY() { y = 20; py = &y; }
- void bar() { printf("CBaseY::bar() y=%d, *py=%d/n", y, *py);
- }
- };
- class CDerived : public CBaseX, public CBaseY
- {
- public:
- int z;
- };情况1:两个无关的类之间的转换
- // Convert between CBaseX* and CBaseY*
- // CBaseX* 和 CBaseY*之间的转换
- CBaseX* pX = new CBaseX();
- // Error, types pointed to are unrelated
- // 错误, 类型指向是无关的
- // CBaseY* pY1 = static_cast<CBaseY*>(pX);
- // Compile OK, but pY2 is not CBaseX
- // 成功编译, 但是 pY2 不是CBaseX
- CBaseY* pY2 = reinterpret_cast<CBaseY*>(pX);
- // System crash!!
- // 系统崩溃!!
- // pY2->bar();正如我们在泛型例子中所认识到的,如果你尝试转换一个对象到另一个无关的类static_cast<>将失败,而reinterpret_cast<>就总是成功“欺骗”编译器:那个对象就是那个无关类。
- 情况2:转换到相关的类
- 1. CDerived* pD = new CDerived();
- 2. printf("CDerived* pD = %x/n", (int)pD);
- 3.
- 4. // static_cast<> CDerived* -> CBaseY* -> CDerived*
- //成功编译,隐式static_cast<>转换
- 5. CBaseY* pY1 = pD;
- 6. printf("CBaseY* pY1 = %x/n", (int)pY1);
- // 成功编译, 现在 pD1 = pD
- 7. CDerived* pD1 = static_cast<CDerived*>(pY1);
- 8. printf("CDerived* pD1 = %x/n", (int)pD1);
- 9.
- 10. // reinterpret_cast
- // 成功编译, 但是 pY2 不是 CBaseY*
- 11. CBaseY* pY2 = reinterpret_cast<CBaseY*>(pD);
- 12. printf("CBaseY* pY2 = %x/n", (int)pY2);
- 13.
- 14. // 无关的 static_cast<>
- 15. CBaseY* pY3 = new CBaseY();
- 16. printf("CBaseY* pY3 = %x/n", (int)pY3);
- // 成功编译,尽管 pY3 只是一个 "新 CBaseY()"
- 17. CDerived* pD3 = static_cast<CDerived*>(pY3);
- 18. printf("CDerived* pD3 = %x/n", (int)pD3); ---------------------- 输出 ---------------------------
- CDerived* pD = 392fb8
- CBaseY* pY1 = 392fbc
- CDerived* pD1 = 392fb8
- CBaseY* pY2 = 392fb8
- CBaseY* pY3 = 390ff0
- CDerived* pD3 = 390fec
- 注意:在将CDerived*用隐式 static_cast<>转换到CBaseY*(第5行)时,结果是(指向)CDerived*(的指针向后) 偏移了4(个字节)(译注:4为int类型在内存中所占字节数)。为了知道static_cast<> 实际如何,我们不得不要来看一下CDerived的内存布局。
- CDerived的内存布局(Memory Layout)
- 本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/zjl_1026_2001/archive/2008/04/03/2246510.aspx
- 如图所示,CDerived的内存布局包括两个对象,CBaseX 和 CBaseY,编译器也知道这一点。因此,当你将CDerived* 转换到 CBaseY*时,它给指针添加4个字节,同时当你将CBaseY*转换到CDerived*时,它给指针减去4。然而,甚至它即便不是一个CDerived你也可以这样做。
- 当然,这个问题只在如果你做了多继承时发生。在你将CDerived转换 到 CBaseX时static_cast<> 和 reinterpret_cast<>是没有区别的。
- 情况3:void*之间的向前和向后转换
- 因为任何指针可以被转换到void*,而void*可以被向后转换到任何指针(对于static_cast<> 和 reinterpret_cast<>转换都可以这样做),如果没有小心处理的话错误可能发生。
- CDerived* pD = new CDerived();
- printf("CDerived* pD = %x/n", (int)pD);
- CBaseY* pY = pD; // 成功编译, pY = pD + 4
- printf("CBaseY* pY = %x/n", (int)pY);
- void* pV1 = pY; //成功编译, pV1 = pY
- printf("void* pV1 = %x/n", (int)pV1);
- // pD2 = pY, 但是我们预期 pD2 = pY - 4
- CDerived* pD2 = static_cast<CDerived*>(pV1);
- printf("CDerived* pD2 = %x/n", (int)pD2);
- // 系统崩溃
- // pD2->bar(); ---------------------- 输出 ---------------------------
- CDerived* pD = 392fb8
- CBaseY* pY = 392fbc
- void* pV1 = 392fbc
- CDerived* pD2 = 392fbc
- 一旦我们已经转换指针为void*,我们就不能轻易将其转换回原类。在上面的例子中,从一个void* 返回CDerived*的唯一方法是将其转换为CBaseY*然后再转换为CDerived*。
- 但是如果我们不能确定它是CBaseY* 还是 CDerived*,这时我们不得不用dynamic_cast<> 或typeid[2]。
- 注释:
- 1. dynamic_cast<>,从另一方面来说,可以防止一个泛型CBaseY* 被转换到CDerived*。
- 2. dynamic_cast<>需要类成为多态,即包括“虚”函数,并因此而不能成为void*。
- 参考:
- 1. [MSDN] C++ Language Reference -- Casting
- 2. Nishant Sivakumar, Casting Basics - Use C++ casts in your VC++.NET programs
- 3. Juan Soulie, C++ Language Tutorial: Type Casting
- 本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/zjl_1026_2001/archive/2008/04/03/2246510.aspx