【C++专题】static_cast, dynamic_cast, const_cast探讨
首先回顧一下C++類型轉(zhuǎn)換:
C++類型轉(zhuǎn)換分為:隱式類型轉(zhuǎn)換和顯式類型轉(zhuǎn)換。
第1部分. 相關(guān)概念解釋
上行轉(zhuǎn)換(up-casting):把子類的指針或引用轉(zhuǎn)換成基類表示。
下行轉(zhuǎn)換(down-casting):把基類指針或引用轉(zhuǎn)換成子類表示。
類型轉(zhuǎn)換不安全性來源于兩個(gè)方面:
??? 其一是類型的窄化轉(zhuǎn)化,會(huì)導(dǎo)致數(shù)據(jù)位數(shù)的丟失;比如int類型轉(zhuǎn)short。float類型轉(zhuǎn)int。
??? 其二是在類繼承鏈中,將父類對(duì)象的地址(指針)強(qiáng)制轉(zhuǎn)化成子類的地址(指針)。
因此上行轉(zhuǎn)換一般是安全的,下行轉(zhuǎn)換很可能是不安全的。子類中包含父類,所以上行轉(zhuǎn)換(只能調(diào)用父類的方法,引用父類的成員變量)一般是安全的。但父類中卻沒有子類的任何信息,而下行轉(zhuǎn)換會(huì)調(diào)用到子類的方法、引用子類的成員變量,這些父類都沒有,所以很容易“指鹿為馬”或者干脆指向不存在的內(nèi)存空間。
??? 值得一說的是,不安全的轉(zhuǎn)換不一定會(huì)導(dǎo)致程序出錯(cuò),比如一些窄化轉(zhuǎn)換在很多場(chǎng)合都會(huì)被頻繁地使用,前提是程序員足夠小心以防止數(shù)據(jù)溢出;下行轉(zhuǎn)換關(guān)鍵看其“本質(zhì)”是什么,比如一個(gè)父類指針指向子類,再將這個(gè)父類指針轉(zhuǎn)成子類指針,這種下行轉(zhuǎn)換就不會(huì)有問題。
第2部分.?隱式類型轉(zhuǎn)換
這種轉(zhuǎn)換是不需要顯式的強(qiáng)制轉(zhuǎn)換的,這部分很簡單。
但是注意一點(diǎn):void指針賦值給其他指定類型指針時(shí),不存在隱式轉(zhuǎn)換,編譯出錯(cuò)。
第3部分. 顯式類型轉(zhuǎn)換
被稱為“強(qiáng)制類型轉(zhuǎn)換”(cast)
C ? ? 風(fēng)格: (type-id)
C++風(fēng)格:?static_cast、dynamic_cast、reinterpret_cast、和const_cast。
下面分別對(duì)這四種顯式轉(zhuǎn)換說明一下:
(1)?static_cast
用法:static_cast?< type-id > ( expression )
說明:該運(yùn)算符把expression轉(zhuǎn)換為type-id類型,但沒有運(yùn)行時(shí)類型檢查來保證轉(zhuǎn)換的安全性。
對(duì)于類只能在有聯(lián)系的指針類型間進(jìn)行轉(zhuǎn)換。可以在繼承體系中把指針轉(zhuǎn)換來、轉(zhuǎn)換去,但是不能轉(zhuǎn)換成繼承體系外的一種類型。
它主要有如下幾種用法:
- 用于類層次結(jié)構(gòu)中基類和子類之間指針或引用的轉(zhuǎn)換。進(jìn)行上行轉(zhuǎn)換(把子類的指針或引用轉(zhuǎn)換成基類表示)是安全的;進(jìn)行下行轉(zhuǎn)換(把基類指針或引用轉(zhuǎn)換成子類指針或引用)時(shí),由于沒有動(dòng)態(tài)類型檢查,所以是不安全的。
- 用于基本數(shù)據(jù)類型之間的轉(zhuǎn)換,如把int轉(zhuǎn)換成char,把int轉(zhuǎn)換成enum。這種轉(zhuǎn)換的安全性也要開發(fā)人員來保證。
- 把void指針轉(zhuǎn)換成目標(biāo)類型的指針(不安全!!)
- 把任何類型的表達(dá)式轉(zhuǎn)換成void類型。
注意:static_cast不能轉(zhuǎn)換掉expression的const、volitale、或者_(dá)_unaligned屬性。
轉(zhuǎn)換安全性示例:
class A { ... }; class B { ... }; class D : public B { ... }; void f(B* pb, D* pd) { D* pd2 = static_cast<D*>(pb); // 不安全, pb可能只是B的指針,但這個(gè)僅僅是個(gè)不安全,代碼還是可以編譯運(yùn)行的 B* pb2 = static_cast<B*>(pd); // 安全的 A* pa2 = static_cast<A*>(pb); // 錯(cuò)誤A與B沒有繼承關(guān)系 ... }
對(duì)于不相關(guān)類指針之間的轉(zhuǎn)換。參見下面的例子:
// class type-casting #include <iostream> using namespace std; class CDummy { float i,j; }; class CAddition { int x,y; public: CAddition (int a, int b) { x=a; y=b; } int result() { return x+y;} }; int main () { CDummy d; CAddition * padd; padd = (CAddition*) &d; cout << padd->result(); return 0; }CAddition與CDummy類沒有任何關(guān)系了,但main()中C風(fēng)格的轉(zhuǎn)換仍是允許的padd = (CAddition*) &d,這樣的轉(zhuǎn)換沒有安全性可言。如果在main()中使用static_cast,像這樣:
int main () { CDummy d; CAddition * padd; padd = static_cast<CAddition*> (&d); cout << padd->result(); return 0; }編譯器就能看到這種不相關(guān)類指針轉(zhuǎn)換的不安全,報(bào)出如下圖所示的錯(cuò)誤:
注意這時(shí)不是以warning形式給出的,而直接是不可通過編譯的error。從提示信息里可以看到,編譯器說如果需要這種強(qiáng)制轉(zhuǎn)換,要使用reinterpret_cast(稍候會(huì)說)或者C風(fēng)格的兩種轉(zhuǎn)換。
總結(jié):static_cast最接近于C風(fēng)格轉(zhuǎn)換了,但在無關(guān)類的類指針之間轉(zhuǎn)換上,有安全性的提升。
?
?
?
(2) dynamic_cast?用法:dynamic_cast?< type-id > ( expression )
說 明:該運(yùn)算符把expression轉(zhuǎn)換成type-id類型的對(duì)象。Type-id必須是類的指針、類的引用或者void *;如果type-id是類指針類型,那么expression也必須是一個(gè)指針,如果type-id是一個(gè)引用,那么expression也必須是一個(gè) 引用。它有三個(gè)重要的約束條件,
??? 第一、必須用于類與子類之間的轉(zhuǎn)換;
??? 第二、必須用于指針或引用類型的轉(zhuǎn)換;
??? 第三、下行轉(zhuǎn)換時(shí)要求基類必須有虛函數(shù)(基類中包含至少一個(gè)虛函數(shù))。
dynamic_cast主要用于類層次間的上行轉(zhuǎn)換和下行轉(zhuǎn)換,還可以用于類之間的交叉轉(zhuǎn)換。在類層次間進(jìn)行上行轉(zhuǎn)換時(shí),dynamic_cast和static_cast的效果是一樣的;在進(jìn)行下行轉(zhuǎn)換時(shí),dynamic_cast具有類型檢查的功能,比static_cast更安全。
?
class Base { public:int m_iNum;virtual void foo(); };class Derived:public Base { public:char *m_szName[100]; };void func(Base *pb) {Derived *pd1 = static_cast<Derived *>(pb);Derived *pd2 = dynamic_cast<Derived *>(pb); } 在上面的代碼段中,如果pb實(shí)際指向一個(gè)Derived類型的對(duì)象,pd1和pd2是一樣的,并且對(duì)這兩個(gè)指針執(zhí)行Derived類型的任何操作都是安全的;
如果pb實(shí)際指向的是一個(gè)Base類型的對(duì)象,那么pd1將是一個(gè)指向該對(duì)象的指針,對(duì)它進(jìn)行Derived類型的操作將是不安全的(如訪問m_szName),而pd2將是一個(gè)空指針(即0,因?yàn)閐ynamic_cast失敗)。
另外要注意:Base要有虛函數(shù),否則會(huì)編譯出錯(cuò);static_cast則沒有這個(gè)限制。這是由于運(yùn)行時(shí)類型檢查需要運(yùn)行時(shí)類型信息,而這個(gè)信息存儲(chǔ)在類 的虛函數(shù)表(關(guān)于虛函數(shù)表的概念,詳細(xì)可見<Inside c++ object model>)中,只有定義了虛函數(shù)的類才有虛函數(shù)表,沒有定義虛函數(shù)的類是沒有虛函數(shù)表的。
為了讓dynamic_cast能正常工作,必須讓編譯器支持運(yùn)行期類型信息(RTTI),所以基類要有虛函數(shù)才可以:
#include <iostream> using namespace std; class CBase { }; class CDerived: public CBase { }; int main() { CBase b; CBase* pb; CDerived d; CDerived* pd; pb = dynamic_cast<CBase*>(&d); // ok: derived-to-base pd = dynamic_cast<CDerived*>(&b); // wrong: base-to-derived }在最后一行代碼有問題,編譯器給的錯(cuò)誤提示如下圖所示
把類的定義改成:
class CBase { virtual void dummy() {} }; class CDerived: public CBase {};再編譯,結(jié)果如下圖所示:
編譯都可以順利通過了。這里我們?cè)趍ain函數(shù)的最后添加兩句話:
cout << pb << endl; cout << pd << endl;輸出pb和pd的指針值,結(jié)果如下:
我們看到一個(gè)奇怪的現(xiàn)象,將父類經(jīng)過dynamic_cast轉(zhuǎn)成子類的指針竟然是空指針!這正是dynamic_cast提升安全性的功能,dynamic_cast可以識(shí)別出不安全的下行轉(zhuǎn)換,但并不拋出異常,而是將轉(zhuǎn)換的結(jié)果設(shè)置成null(空指針)。
再舉一個(gè)例子:
#include <iostream> #include <exception> using namespace std; class CBase { virtual void dummy() {} }; class CDerived: public CBase { int a; }; int main () { try { CBase * pba = new CDerived; CBase * pbb = new CBase; CDerived * pd; pd = dynamic_cast<CDerived*>(pba); if (pd==0) cout << "Null pointer on first type-cast" << endl; pd = dynamic_cast<CDerived*>(pbb); if (pd==0) cout << "Null pointer on second type-cast" << endl; } catch (exception& e) { cout << "Exception: " << e.what(); } return 0; }輸出結(jié)果是:Null pointer on second type-cast
兩個(gè)dynamic_cast都是下行轉(zhuǎn)換,第一個(gè)轉(zhuǎn)換是安全的,因?yàn)橹赶驅(qū)ο蟮谋举|(zhì)是子類,轉(zhuǎn)換的結(jié)果使子類指針指向子類,天經(jīng)地義;第二個(gè)轉(zhuǎn)換是不安全的,因?yàn)橹赶驅(qū)ο蟮谋举|(zhì)是父類,“指鹿為馬”或指向不存在的空間很可能發(fā)生!
另外,dynamic_cast還支持交叉轉(zhuǎn)換(cross cast)。如下代碼所示。
class Base { public:int m_iNum;virtual void f(){} };class Derived1 : public Base { };class Derived2 : public Base { };void foo() {derived1 *pd1 = new Drived1;pd1->m_iNum = 100;Derived2 *pd2 = static_cast<Derived2 *>(pd1); //compile errorDerived2 *pd2 = dynamic_cast<Derived2 *>(pd1); //pd2 is NULLdelete pd1; }在函數(shù)foo中,使用static_cast進(jìn)行轉(zhuǎn)換是不被允許的,將在編譯時(shí)出錯(cuò);而使用dynamic_cast的轉(zhuǎn)換則是允許的,結(jié)果是空指針。
(3)?reinpreter_cast
用法:reinpreter_cast<type-id> (expression)
說明:type-id必須是一個(gè)指針、引用、算術(shù)類型、函數(shù)指針或者成員指針。它可以把一個(gè)指針轉(zhuǎn)換成一個(gè)整數(shù),也可以把一個(gè)整數(shù)轉(zhuǎn)換成一個(gè)指針(先把一個(gè)指針轉(zhuǎn)換成一個(gè)整數(shù),在把該整數(shù)轉(zhuǎn)換成原類型的指針,還可以得到原先的指針值)。
(4)?const_cast
用法:const_cast<type_id> (expression)
說明:該運(yùn)算符用來修改類型的const或volatile屬性。除了const 或volatile修飾之外, type_id和expression的類型是一樣的。
常量指針被轉(zhuǎn)化成非常量指針,并且仍然指向原來的對(duì)象;常量引用被轉(zhuǎn)換成非常量引用,并且仍然指向原來的對(duì)象;常量對(duì)象被轉(zhuǎn)換成非常量對(duì)象。
class B { public:int m_iNum; }void foo() {const B b1;b1.m_iNum = 100; //comile errorB b2 = const_cast<B>(b1);b2. m_iNum = 200; //fine }上面的代碼編譯時(shí)會(huì)報(bào)錯(cuò),因?yàn)閎1是一個(gè)常量對(duì)象,不能對(duì)它進(jìn)行改變;使用const_cast把它轉(zhuǎn)換成一個(gè)常量對(duì)象,就可以對(duì)它的數(shù)據(jù)成員任意改變。注意:b1和b2是兩個(gè)不同的對(duì)象。
最后的總結(jié):
1.哪些強(qiáng)制的顯示轉(zhuǎn)換時(shí)非法的?
不想關(guān)的兩個(gè)對(duì)象顯示的轉(zhuǎn)換時(shí)不合法的。
派生類和基類之間的下行轉(zhuǎn)換是不合法的,這里說的下行轉(zhuǎn)換指的是把一個(gè)真正的基類對(duì)象或者指向基類對(duì)象的指針轉(zhuǎn)換成派生類對(duì)象或者派生類對(duì)象的指針。
上面的兩種情況都是不合法的,上面說的不合法的意思是這種轉(zhuǎn)換是不合理的,轉(zhuǎn)換完了之后得到的對(duì)象是不能用的。
下面還有一個(gè)概念是不安全,所謂的安全是能夠轉(zhuǎn)換,并且轉(zhuǎn)換完了之后能夠正確的使用;或者轉(zhuǎn)換完了不能夠使用(上面的兩種情況),但是編譯器會(huì)報(bào)錯(cuò),不讓我對(duì)他們進(jìn)行強(qiáng)制轉(zhuǎn)換。不安全的意思就是明明轉(zhuǎn)換完了不能夠合理的使用,你還給我轉(zhuǎn)換了讓我用,這就是不安全。
2.各種顯示的轉(zhuǎn)換是怎么解決這些問題的?
C語言的強(qiáng)制類型轉(zhuǎn)換,是能夠轉(zhuǎn)換各種的指針的,這里是很不安全的,這里的不安全的意思是能夠強(qiáng)制轉(zhuǎn)換,但是轉(zhuǎn)換完了之后不能用。C語言的轉(zhuǎn)換是最暴力的,最不安全的。
static_cast:是不相關(guān)的兩個(gè)對(duì)象之間的轉(zhuǎn)換變得安全,這里的安全的意思是他不會(huì)讓你顯示轉(zhuǎn)換,編譯會(huì)報(bào)錯(cuò),之所以不讓你強(qiáng)制轉(zhuǎn)換,是因?yàn)檗D(zhuǎn)了之后也不能用。但是下行轉(zhuǎn)換它還是暴力的轉(zhuǎn)換了,很可能轉(zhuǎn)換得到一個(gè)不能使用的結(jié)果。這就是不安全的。
舉個(gè)例子,把基類指針顯式的轉(zhuǎn)換為派生類指針,如果基類指針指向了派生了,這時(shí)還可以。但是如果基類指針真的就指向了一個(gè)基類,那么這種轉(zhuǎn)換仍然會(huì)進(jìn)行,但是得到的結(jié)果卻不能使用。
dynamic_cast:使下行轉(zhuǎn)換也變得安全,如果不小心把指向基類的基類指針轉(zhuǎn)換成了派生類指針,這時(shí)它會(huì)告訴你轉(zhuǎn)換失敗,返回一個(gè)NULL指針回來,這樣的做法就是安全的。因?yàn)榈貌坏胶玫慕Y(jié)果的轉(zhuǎn)換它不會(huì)去做,提示轉(zhuǎn)換失敗。
注意:dynamic_cast必須提供運(yùn)行時(shí)信息,也就是說基類中必須有虛函數(shù)。
對(duì)于來自同一個(gè)基類的兩個(gè)派生類,這兩個(gè)派生類顯式轉(zhuǎn)換就是交叉轉(zhuǎn)換,這種轉(zhuǎn)換在static_cast中屬于不想關(guān)的兩個(gè)類,會(huì)有compile錯(cuò)誤。在dynamic_cast中會(huì)轉(zhuǎn)換失敗,返回NULL,所以都是安全的。
?一道例題
class ClassA {public:virtual ~ ClassA(){}virtual void FunctionA(){} }; class ClassB {public:virtual void FunctionB(){} }; class ClassC: public ClassA, public ClassB {public: }; ClassC aObject; ClassA *pA = &aObject; ClassB *pB = &aObject; ClassC *pC = &aObject; 假設(shè)定義了ClassA* pA2,下面正確的代碼是: pA2=static_cast<ClassA*>(pB); void* pVoid=static_cast<void*>(pB); pA2=static_cast<ClassA*>(pVoid); pA2=pB; pA2=static_cast<ClassA*>(static_cast<ClassC*>(pB));//正確答案BD分析:A是不想關(guān)的兩個(gè)類轉(zhuǎn)換,編譯器會(huì)報(bào)錯(cuò),因?yàn)閟tatic_cast是這種轉(zhuǎn)換變得安全了。B直接跟C語言的暴力轉(zhuǎn)換一個(gè),強(qiáng)制轉(zhuǎn)換成void在暴力強(qiáng)制轉(zhuǎn)化成想要的東西。程序是不會(huì)報(bào)錯(cuò)的,但是最后轉(zhuǎn)換的結(jié)果不能用。C這種隱式的存在是不存在的。D先用static_cast下行轉(zhuǎn)換,雖然不安全,但是pB確實(shí)是指向ClassC的指針,是可以轉(zhuǎn)換的,不會(huì)報(bào)錯(cuò)。然后用static_cast上行轉(zhuǎn)換。
轉(zhuǎn)載于:https://www.cnblogs.com/stemon/p/4716873.html
總結(jié)
以上是生活随笔為你收集整理的【C++专题】static_cast, dynamic_cast, const_cast探讨的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 超级课程表app怎么改学校(超级简历Wo
- 下一篇: 重装战姬搭配 电脑重装系统