<pre name="code" class="cpp">static_cast, dynamic_cast, reinterpret_cast, const_cast區別比較
(使用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++ 常用類型轉換的區別
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.