內容簡介
本文對四種標準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<D *>(pb);
D *pd2 = dynamic_cast<D *>(pb);
}
這里,使用 dynamic_cast
進行轉換,如果出現了把指向父類對象的指針轉換成了子類的指針的時候,就會返回空值。
在上面的代碼段中,如果 pb
指向一個 D
類型的對象, pd1
和 pd2
是一樣的,并且對這兩個指針執行 D
類型的任何操作都是安全的;但是,如果 pb
指向的是一個 B
類型的對象,那么 pd1
將是一個指向該對象的指針,對它進行 D
類型的操作將是不安全的(如訪問 m_szName
),而 pd2
將是一個空指針。(大多數面向對象編程中,經常會在代碼中出現父類型的指針指向子類對象,然后在合適的時候轉換為子類對象類型,直接代碼中看不出指針所知對象究竟是父類還是子類,通過這個 dynamic_cast
可以檢查類型,達到安全性)。
另外要注意:如果使用 dynamic_cast
, B
(父類)要有虛函數,否則會編譯出錯; static_cast
則沒有這個限制。這是由于運行時類型檢查需要運行時類型信息,而這個信息存儲在類的虛函數表(關于虛函數表的概念,詳細可見 <Inside c++ object model>
)中,只有定義了虛函數的類才有虛函數表,沒有定義虛函數的類是沒有虛函數表的。
類之間的交叉轉換
代碼如下:
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<D *>(pb); //compile error???實踐好象沒有編譯錯誤
D *pd2 = dynamic_cast<D *>(pb); //pd2 is NULL
delete pb;
}
這里,可見,如果出現了交叉轉換的情況那么 dynamic_cast
將會返回空值。
在函數 foo
中,使用 static_cast
進行轉換是不被允許的,將在編譯時出錯(實踐中并沒有報錯);而使用 dynamic_cast
的轉換則是允許的,結果是空指針。 保險應當使用 dynamic_cast
。
reinterpret_cast
用法: reinterpret_cast<type-id> (expression)
功能
它可以把一個指針轉換成一個整數,也可以把一個整數轉換成一個指針(先把一個指針轉換成一個整數,再把該整數轉換成原類型的指針,還可以得到原先的指針值)。 type-id
必須是一個指針、引用、算術類型、函數指針或者成員指針。
描述
reinterpret_cast
是C++里的強制類型轉換符,支持任何轉換,但僅僅是如它的名字所描述的“重解釋”而已。也就是說:操作符修改了操作數類型,但僅僅是重新解釋了給出的對象的比特模型而沒有進行二進制轉換。
例如:
int *n= new int;
double *d=reinterpret_cast<double*> (n);
在進行計算以后, d
包含無用值.這是因為 reinterpret_cast
僅僅是復制 n
的比特位到 d
, 沒有進行必要的分析。
reinterpret_cast
是為了映射到一個完全不同類型的意思,這個關鍵詞在我們需要把類型映射回原有類型時用到它。我們映射到的類型僅僅是為了故弄玄虛和其他目的,這是所有映射中最危險的(C++編程思想中的原話)。將 static_cast
和 reinterpret_cast
對比一下進行解釋,比較容易理解: static_cast
操作符修改了操作數類型,但是 reinterpret_cast
僅僅是重新解釋了給出的對象的比特模型而沒有進行二進制轉換。
例如:
int n=9;
double d=static_cast<double>(n);
上面的例子中, 我們將一個變量從 int
轉換到 double
。這些類型的二進制表達式是不同的,所以將整數9轉換到雙精度整數9, static_cast
需要正確地為雙精度整數 d
補足比特位。其結果為 9.0
。
而 reinterpret_cast
的行為卻不同:
int n=9;
double d=reinterpret_cast<double & >(n);
這里, 和 static_cast
不同,在進行計算以后, d
包含無用值。這是因為 reinterpret_cast
僅僅是復制 n
的比特位到 d
, 沒有進行必要的分析。
因此, 需要謹慎使用 reinterpret_cast
。
舉例
這個例子,將 static_cast
和 reinterpret_cast
對比進行測試,具體的輸出參見其中的注釋。
1 #include <iostream>
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================="<<endl;
32 //======static cast<>的使用:
33 int n = static_cast<int>(f); //成功編譯
34 cout<<"n is :"<<n<<endl;//n = 12
35 //int* pn = static_cast<int*>(pf);//編譯錯誤,指向的類型是無關的,不能將指針指向無關的類型
36 void* pv = static_cast<void*>(pf);//編譯成功
37 int* pn2 = static_cast<int*>(pv);//成功編譯, 但是 *pn2是無意義的內存(rubbish)
38 cout<<"pf is:"<<pf<<",pv is:"<<pv<<",pn2 is:"<<pn2<<endl;//三者值一樣
39 cout<<"*pf is:"<<*pf<<",*pn2 is:"<<*pn2<<endl;//pf=12.3,pn2是無用值,注意無法使用"*pv"因為編譯錯。
40
41 //======reinterpret_cast<>的使用:
42 //int i = reinterpret_cast<int>(f);//編譯錯誤,類型‘float’到類型‘int’的轉換無效.
43 //成功編譯, 但是 *pn 實際上是無意義的內存,和 *pn2一樣
44 int* pi = reinterpret_cast<int*>(pf);
45 cout<<"pf is:"<<pf<<",pi is:"<<pi<<endl;//值一樣
46 cout<<"*pf is:"<<*pf<<",*pi is:"<<*pi<<endl;//pi是無用值,和pn2一樣。
47
48
49 //對象類型的轉換
50 cout<<"=================Class Cast================="<<endl;
51 CBaseX cx;
52 CBaseY cy;
53 CDerived cd;
54
55 CDerived* pD = &cd;
56 CBaseX *pX = &cx;
57 CBaseY *pY = &cy;
58 cout<<"CDerived* pD = "<<pD<<endl;
59
60 //======static_cast<>的使用:
61 CBaseY* pY1 = pD; //隱式static_cast轉換
62 //不一樣是因為多繼承,pD還要前移動以便也指向CBaseX.
63 cout<<"CDerived* pD = "<<pD<<",CBaseY* pY1 = "<<pY1<<endl;//pY1=pD+4!!!!!!
64
65 //CDerived* pD1 = pY1;//編譯錯誤,類型 ‘CBaseY*’ 到類型 ‘CDerived*’ 的轉換無效
66 CDerived* pD1 = static_cast<CDerived*>(pY1);//成功編譯
67 cout<<"CDerived* pD1 = "<<pD1<<endl;//現在 pD1 = pD
68
69 //pX = static_cast<CBaseX*>(pY);//編譯錯誤,從類型 ‘CBaseY*’ 到類型 ‘CBaseX*’ 中的 static_cast 無效。
70 pD1 = static_cast<CDerived*>(pY);//竟然可以編譯通過!!!!!!
71 cout<<"CDerived* pD1 = "<<pD1<<",CBaseY *pY = "<<pY<<endl;//現在 pD1 = pY-4
72 //======reinterpret_cast<>的使用:
73 CBaseY* pY2 = reinterpret_cast<CBaseY*>(pD);// 成功編譯, 但是 pY2 不是 CBaseY*
74 cout<<"CDerived* pD = "<<pD<<",CBaseY* pY2 = "<<pY2<<endl;//pY2=pD!!!!!!
75
76 //======通過void的轉換注意:
77 CBaseY* ppY = pD;
78 cout<<"CDerived* pD = "<<pD<<",CBaseY* ppY = "<<ppY<<endl;//ppY = pD + 4
79
80 void* ppV1 = ppY; //成功編譯
81 cout<<"CBaseY* ppY = "<<ppY<<",void* ppV1 = "<<ppV1<<endl;//ppV1 = ppY
82
83 //CDerived* ppD2 = ppV1;//編譯錯誤,類型‘void*’ 到類型 ‘CDerived*’的轉換無效
84 CDerived* ppD2 = static_cast<CDerived*>(ppV1);
85 cout<<"CDerived* ppD2 = "<<ppD2<<endl;//ppD2 = ppY, 但是我們預期 ppD2 = ppY - 4 = pD
86 //ppD2->bar();//系統崩潰,段錯誤
87 return 0;
88 }
這里,需要注意的地方是:
-
第63行中基類指針
pY1
被賦予子類指針pD
后,pY1=pD+4
而不是pD
,因為pD
是多繼承,pD
還要前移動以便也指向CBaseX
.內存布局大致如下:
+CDerived------------------+ | +CBase X--------+ |\ | | int x | | 4 bytes | +---------------+ |/ | | | +CBase Y--------+ | | | int y,*py | | | +---------------+ | +--------------------------+
第69行和70行的可以將父類指針用
static_cast
強制轉換成子類指針,但是兩個無關的類的指針之間卻不能轉換。第74行中使用
reinterpret_cast
將子類指針強制轉換賦給父類指針后,卻沒有像static_cast
那樣將父類指針位置調整以指向正確的對象位置,這樣導致雖然兩者值是一樣的,但是父指針所指向的內容卻不是父對象了。-
第76行之后使用
void
將子類轉換成父類再轉回子類,卻無法使用了。因為任何指針可以被轉換到
void*
,而void*
可以被向后轉換到任何指針(對于static_cast<>
和reinterpret_cast<>
轉換都可以這樣做),如果沒有小心處理的話錯誤可能發生。一旦我們已經轉換指針為void*
,我們就不能輕易將其轉換回原類(因為原類的信息在代碼中看不到了),所以使用void
轉換的時候一定要小心。在上面的例子中,從一個void*
返回CDerived*
的唯一方法是將其轉換為CBaseY*
然后再轉換為CDerived*
。但是如果我們不能確定它是CBaseY*
還是CDerived*
,這時我們不得不用dynamic_cast<>
或typeid[2]
(dynamic_cast<>
需要類成為多態,即包括“虛”函數,并因此而不能成為void*
)。
[其它]
dynamic_cast<>
,從另一方面來說,可以防止一個泛型 CBaseY*
被轉換到 CDerived*
。
const_cast
用法: const_cast<type_id> (expression)
功能
該運算符用來修改類型的 const
或 volatile
屬性。除了 const
或 volatile
修飾之外, type_id
和 expression
的類型是一樣的。
描述
const_cast
剝離一個對象的 const
屬性,允許對常量進行修改。
- 常量指針被轉化成非常量指針,并且仍然指向原來的對象;
- 常量引用被轉換成非常量引用,并且仍然指向原來的對象;
- 常量對象被轉換成非常量對象。
Voiatile
和 const
類似。參見后面的例子可以了解更多信息。
舉例
給出的源代碼如下:
1 #include <iostream>
2 using std::cout;
3 using std::endl;
4
5 class CTest
6 {
7 public:
8 CTest(int i){m_val = i;cout<<"construction"<<m_val<<endl;}
9 ~CTest(){cout<<"destructionn"<<endl;}
10 void SelfAdd(){m_val++;};
11 int m_val;
12 };
13
14 int main(int argc, char *argv[])
15 {
16 const int ic = 100;
17 //int cc = const_cast<int>(ic);//編譯錯誤
18 int cc = const_cast<int&>(ic);
19 cout<<cc<<endl;//輸出100
20 //const_cast<int &>(&ic)=200;//編譯錯誤,從類型 ‘const int*’ 到類型 ‘int&’ 中的 const_cast 無效
21 const_cast<int &>(ic)=200;
22 cout<<ic<<endl;//輸出100
23 cout<<*(&ic)<<endl;//輸出100
24 //int *pc = ⁣//編譯錯誤,從類型 ‘const int*’ 到類型 ‘int*’ 的轉換無效
25 const int *pc=⁣
26 //const_cast<int &>(pc)=200;//編譯錯誤,從類型 ‘const int**’ 到類型 ‘int*’ 中的 const_cast 無效
27 const_cast<int &>(ic)=200;
28 //printf("%d,%d/n", ic, *pc);
29 cout<<ic<<','<<*pc<<endl;//100,200
30 //int *ppc = const_cast<int*>(ic);//編譯錯誤
31 int *ppc = const_cast<int*>(&ic);
32 *ppc = 300;
33 cout<<ic<<','<<*ppc<<endl;//100,300
34
35 const CTest test(1000);
36 CTest test2(1050);
37 //test = test2;//編譯錯誤,無法給常量賦值
38 const_cast<CTest &>(test)= test2;
39 cout<<test.m_val<<endl;//輸出1050
40 }
這里,結果輸出參見每行代碼相應的注釋。根據結果可知:
- 凡是對結構體或類進行這個轉換,都是成功的。
- 對
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<int>(a);
而使用引用的方式,如下卻是正確的:
const int a = 5;
int aa = const_cast<int&>(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<int*>(pf);//編譯錯誤,指向的類型是無關的,不能將指針指向無關的類型
對于復合類型(例如類、結構、聯合)它允許轉換子對象地址賦值給父指針,也允許轉換父對象地址賦值給子指針,但是不允許兩個無關的類之間的轉換,所以如下是錯誤的:
CBaseX *pX = &cx; CBaseY *pY = &cy; pX = static_cast<CBaseX*>(pY);//編譯錯誤,從類型 ‘CBaseY*’ 到類型 ‘CBaseX*’ 中的 static_cast 無效。
-
dynamic_cast
在運行期間發生錯誤,它只允許它允許轉換子對象地址賦值給父指針,其它情況都返回空。例如:
B *pb = new B; D *pd = dynamic_cast<D *>(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<a&>(b)
可以通過而 function(static<a&>(c))
不能通過編譯,因為在編譯的時候編譯器已經知道 c
和 a
的類型不符,因此 static_cast
可以保證安全。
reinterpret_cast
可任意轉換32bit的指針、實例
就是說,它可以轉換任意一個32bit整數,包括所有的指針和整數??梢园讶魏握麛缔D成指針,也可以把任何指針轉成整數,以及把指針轉化為任意類型的指針,威力最為強大!但不能將非32bit的實例轉成指針??傊?,只要是32bit的東東,怎么轉都行!
對于剛剛說的例子,下面我們騙一下編譯器,先把 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
不檢測
以上是從網上搜集的,以及根據自己的理解對C++中四種操作符號的總結,如有不準確的地方,感謝讀者的告知。另外文件的圖示內容由于格式轉換所以不太準確,可以存成文本格式的。_
參考資料
http://zhidao.baidu.com/question/81318972.html
http://blog.csdn.net/guogangj/article/details/1545119