【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)換不安全性來源于兩個方面:
??? 其一是類型的窄化轉(zhuǎn)化,會導(dǎo)致數(shù)據(jù)位數(shù)的丟失;比如int類型轉(zhuǎn)short。float類型轉(zhuǎn)int。
??? 其二是在類繼承鏈中,將父類對象的地址(指針)強(qiáng)制轉(zhuǎn)化成子類的地址(指針)。
因此上行轉(zhuǎn)換一般是安全的,下行轉(zhuǎn)換很可能是不安全的。子類中包含父類,所以上行轉(zhuǎn)換(只能調(diào)用父類的方法,引用父類的成員變量)一般是安全的。但父類中卻沒有子類的任何信息,而下行轉(zhuǎn)換會調(diào)用到子類的方法、引用子類的成員變量,這些父類都沒有,所以很容易“指鹿為馬”或者干脆指向不存在的內(nèi)存空間。
??? 值得一說的是,不安全的轉(zhuǎn)換不一定會導(dǎo)致程序出錯,比如一些窄化轉(zhuǎn)換在很多場合都會被頻繁地使用,前提是程序員足夠小心以防止數(shù)據(jù)溢出;下行轉(zhuǎn)換關(guān)鍵看其“本質(zhì)”是什么,比如一個父類指針指向子類,再將這個父類指針轉(zhuǎn)成子類指針,這種下行轉(zhuǎn)換就不會有問題。
第2部分.?隱式類型轉(zhuǎn)換
這種轉(zhuǎn)換是不需要顯式的強(qiáng)制轉(zhuǎn)換的,這部分很簡單。
但是注意一點:void指針賦值給其他指定類型指針時,不存在隱式轉(zhuǎn)換,編譯出錯。
第3部分. 顯式類型轉(zhuǎn)換
被稱為“強(qiáng)制類型轉(zhuǎn)換”(cast)
C ? ? 風(fēng)格: (type-id)
C++風(fēng)格:?static_cast、dynamic_cast、reinterpret_cast、和const_cast。
下面分別對這四種顯式轉(zhuǎn)換說明一下:
(1)?static_cast
用法:static_cast?< type-id > ( expression )
說明:該運算符把expression轉(zhuǎn)換為type-id類型,但沒有運行時類型檢查來保證轉(zhuǎn)換的安全性。
對于類只能在有聯(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)換成子類指針或引用)時,由于沒有動態(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的指針,但這個僅僅是個不安全,代碼還是可以編譯運行的 B* pb2 = static_cast<B*>(pd); // 安全的 A* pa2 = static_cast<A*>(pb); // 錯誤A與B沒有繼承關(guān)系 ... }
對于不相關(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)換的不安全,報出如下圖所示的錯誤:
注意這時不是以warning形式給出的,而直接是不可通過編譯的error。從提示信息里可以看到,編譯器說如果需要這種強(qiáng)制轉(zhuǎn)換,要使用reinterpret_cast(稍候會說)或者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 )
說 明:該運算符把expression轉(zhuǎn)換成type-id類型的對象。Type-id必須是類的指針、類的引用或者void *;如果type-id是類指針類型,那么expression也必須是一個指針,如果type-id是一個引用,那么expression也必須是一個 引用。它有三個重要的約束條件,
??? 第一、必須用于類與子類之間的轉(zhuǎn)換;
??? 第二、必須用于指針或引用類型的轉(zhuǎn)換;
??? 第三、下行轉(zhuǎn)換時要求基類必須有虛函數(shù)(基類中包含至少一個虛函數(shù))。
dynamic_cast主要用于類層次間的上行轉(zhuǎn)換和下行轉(zhuǎn)換,還可以用于類之間的交叉轉(zhuǎn)換。在類層次間進(jìn)行上行轉(zhuǎn)換時,dynamic_cast和static_cast的效果是一樣的;在進(jìn)行下行轉(zhuǎn)換時,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實際指向一個Derived類型的對象,pd1和pd2是一樣的,并且對這兩個指針執(zhí)行Derived類型的任何操作都是安全的;
如果pb實際指向的是一個Base類型的對象,那么pd1將是一個指向該對象的指針,對它進(jìn)行Derived類型的操作將是不安全的(如訪問m_szName),而pd2將是一個空指針(即0,因為dynamic_cast失敗)。
另外要注意:Base要有虛函數(shù),否則會編譯出錯;static_cast則沒有這個限制。這是由于運行時類型檢查需要運行時類型信息,而這個信息存儲在類 的虛函數(shù)表(關(guān)于虛函數(shù)表的概念,詳細(xì)可見<Inside c++ object model>)中,只有定義了虛函數(shù)的類才有虛函數(shù)表,沒有定義虛函數(shù)的類是沒有虛函數(shù)表的。
為了讓dynamic_cast能正常工作,必須讓編譯器支持運行期類型信息(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 }在最后一行代碼有問題,編譯器給的錯誤提示如下圖所示
把類的定義改成:
class CBase { virtual void dummy() {} }; class CDerived: public CBase {};再編譯,結(jié)果如下圖所示:
編譯都可以順利通過了。這里我們在main函數(shù)的最后添加兩句話:
cout << pb << endl; cout << pd << endl;輸出pb和pd的指針值,結(jié)果如下:
我們看到一個奇怪的現(xiàn)象,將父類經(jīng)過dynamic_cast轉(zhuǎn)成子類的指針竟然是空指針!這正是dynamic_cast提升安全性的功能,dynamic_cast可以識別出不安全的下行轉(zhuǎn)換,但并不拋出異常,而是將轉(zhuǎn)換的結(jié)果設(shè)置成null(空指針)。
再舉一個例子:
#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
兩個dynamic_cast都是下行轉(zhuǎn)換,第一個轉(zhuǎn)換是安全的,因為指向?qū)ο蟮谋举|(zhì)是子類,轉(zhuǎn)換的結(jié)果使子類指針指向子類,天經(jīng)地義;第二個轉(zhuǎ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)換是不被允許的,將在編譯時出錯;而使用dynamic_cast的轉(zhuǎn)換則是允許的,結(jié)果是空指針。
(3)?reinpreter_cast
用法:reinpreter_cast<type-id> (expression)
說明:type-id必須是一個指針、引用、算術(shù)類型、函數(shù)指針或者成員指針。它可以把一個指針轉(zhuǎn)換成一個整數(shù),也可以把一個整數(shù)轉(zhuǎn)換成一個指針(先把一個指針轉(zhuǎn)換成一個整數(shù),在把該整數(shù)轉(zhuǎn)換成原類型的指針,還可以得到原先的指針值)。
(4)?const_cast
用法:const_cast<type_id> (expression)
說明:該運算符用來修改類型的const或volatile屬性。除了const 或volatile修飾之外, type_id和expression的類型是一樣的。
常量指針被轉(zhuǎn)化成非常量指針,并且仍然指向原來的對象;常量引用被轉(zhuǎn)換成非常量引用,并且仍然指向原來的對象;常量對象被轉(zhuǎn)換成非常量對象。
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 }上面的代碼編譯時會報錯,因為b1是一個常量對象,不能對它進(jìn)行改變;使用const_cast把它轉(zhuǎn)換成一個常量對象,就可以對它的數(shù)據(jù)成員任意改變。注意:b1和b2是兩個不同的對象。
最后的總結(jié):
1.哪些強(qiáng)制的顯示轉(zhuǎn)換時非法的?
不想關(guān)的兩個對象顯示的轉(zhuǎn)換時不合法的。
派生類和基類之間的下行轉(zhuǎn)換是不合法的,這里說的下行轉(zhuǎn)換指的是把一個真正的基類對象或者指向基類對象的指針轉(zhuǎn)換成派生類對象或者派生類對象的指針。
上面的兩種情況都是不合法的,上面說的不合法的意思是這種轉(zhuǎn)換是不合理的,轉(zhuǎn)換完了之后得到的對象是不能用的。
下面還有一個概念是不安全,所謂的安全是能夠轉(zhuǎn)換,并且轉(zhuǎn)換完了之后能夠正確的使用;或者轉(zhuǎn)換完了不能夠使用(上面的兩種情況),但是編譯器會報錯,不讓我對他們進(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)的兩個對象之間的轉(zhuǎn)換變得安全,這里的安全的意思是他不會讓你顯示轉(zhuǎn)換,編譯會報錯,之所以不讓你強(qiáng)制轉(zhuǎn)換,是因為轉(zhuǎn)了之后也不能用。但是下行轉(zhuǎn)換它還是暴力的轉(zhuǎn)換了,很可能轉(zhuǎn)換得到一個不能使用的結(jié)果。這就是不安全的。
舉個例子,把基類指針顯式的轉(zhuǎn)換為派生類指針,如果基類指針指向了派生了,這時還可以。但是如果基類指針真的就指向了一個基類,那么這種轉(zhuǎn)換仍然會進(jìn)行,但是得到的結(jié)果卻不能使用。
dynamic_cast:使下行轉(zhuǎn)換也變得安全,如果不小心把指向基類的基類指針轉(zhuǎn)換成了派生類指針,這時它會告訴你轉(zhuǎn)換失敗,返回一個NULL指針回來,這樣的做法就是安全的。因為得不到好的結(jié)果的轉(zhuǎn)換它不會去做,提示轉(zhuǎn)換失敗。
注意:dynamic_cast必須提供運行時信息,也就是說基類中必須有虛函數(shù)。
對于來自同一個基類的兩個派生類,這兩個派生類顯式轉(zhuǎn)換就是交叉轉(zhuǎn)換,這種轉(zhuǎn)換在static_cast中屬于不想關(guān)的兩個類,會有compile錯誤。在dynamic_cast中會轉(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)的兩個類轉(zhuǎn)換,編譯器會報錯,因為static_cast是這種轉(zhuǎn)換變得安全了。B直接跟C語言的暴力轉(zhuǎn)換一個,強(qiáng)制轉(zhuǎn)換成void在暴力強(qiáng)制轉(zhuǎn)化成想要的東西。程序是不會報錯的,但是最后轉(zhuǎn)換的結(jié)果不能用。C這種隱式的存在是不存在的。D先用static_cast下行轉(zhuǎn)換,雖然不安全,但是pB確實是指向ClassC的指針,是可以轉(zhuǎn)換的,不會報錯。然后用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
- 下一篇: 重装战姬搭配 电脑重装系统