C++中的四種類(lèi)型轉(zhuǎn)換
1 引子
這篇筆記是根據(jù)StackOverflow上面的一個(gè)問(wèn)題整理而成,主要內(nèi)容是對(duì)C/C++當(dāng)中四種類(lèi)型轉(zhuǎn)換操作進(jìn)行舉例說(shuō)明。在之前其實(shí)對(duì)它們都是有所了解的,而隨著自己在進(jìn)行總結(jié),并敲了一些測(cè)試示例代碼進(jìn)行驗(yàn)證之后,對(duì)它們的理解又深刻了一些。
總所周知,在C++ 當(dāng)中引入了四種新的類(lèi)型轉(zhuǎn)換操作符:static_cast, dynamic_cast, reinterpret_cast,還有const_cast。就自己見(jiàn)過(guò)的一些C++代碼當(dāng)中,它們的使用其實(shí)并不普遍。不少程序員依然樂(lè)于去使用C-like的類(lèi)型轉(zhuǎn)換,因?yàn)樗鼜?qiáng)大且編寫(xiě)起來(lái)又簡(jiǎn)單。據(jù)說(shuō)C-Like類(lèi)型轉(zhuǎn)換操作符的作用實(shí)際上已經(jīng)包括了static_cast, const_cast和reinterpret_cast三種操作符,你相信嗎?一起來(lái)著看。
注:上面提到的C-Like類(lèi)型轉(zhuǎn)換操作有如下的兩種形式,這一點(diǎn)大家一定都不會(huì)陌生。
(new-type) expression new-type (expression)
2 static_cast vs dynamic_cast
之所以把static_cast與dynamic_cast兩兄弟放在一起是因?yàn)樗鼈儍烧邔?duì)比起來(lái)更容易記得住。首先,從名稱(chēng)上面它們就有語(yǔ)義相對(duì)的關(guān)系,一“靜”一“動(dòng)”。另外,在功能上面也在一定程度上體現(xiàn)了這一對(duì)比的特性,如dynamic_cast的Run-time Checkingt,static_cast在編譯時(shí)增加的類(lèi)型檢測(cè)。簡(jiǎn)單而言:
static_cast: 1)完成基礎(chǔ)數(shù)據(jù)類(lèi)型,2)同一個(gè)繼承體系中類(lèi)型的轉(zhuǎn)換
dynamic_cast:使用多態(tài)的場(chǎng)景,增加了一層對(duì)真實(shí)調(diào)用對(duì)象類(lèi)型的檢查
2.1 從C-Like到static_cast
static_cast對(duì)于基礎(chǔ)類(lèi)型如int, float, char以及基礎(chǔ)類(lèi)型對(duì)應(yīng)指針的處理大多情況下恰如C-Like的轉(zhuǎn)換一樣,不過(guò)static_cast會(huì)來(lái)得更加安全。
char c = 10; // 1 個(gè)字節(jié) int *p = (int *)&c; // 4 個(gè)字節(jié)(32bit platform) *p = 5; // 內(nèi)存踩臟 int *q = static_cast<int *>(&c); // 使用static_cast可在編譯階段將該錯(cuò)誤檢查出來(lái)。
對(duì)于自定義類(lèi)型的處理,相比C-Like而言,它也多了一層保護(hù),也就是它不支持在不屬于同一繼承體系的類(lèi)型之間進(jìn)行轉(zhuǎn)換。但是C-Like就可以辦到,看下面這個(gè)例子:
#include <iostream> class A { public: A(){} ~A(){} private: int i, j; }; class C { public: C(){} ~C(){} void printC() { std::cout <<"call printC() in class C" <<std::endl; } private: char c1, c2; }; int main() { A *ptrA = new A; //C *ptrC = static_cast<C *>(ptrA); // 編譯無(wú)法通過(guò),提示: // In function ‘int main()': // error: invalid static_cast from type ‘A*' to type ‘C*' C *ptrC = (C *)(ptrA); ptrC->printC(); // 編譯正常通過(guò)。 // 盡管這個(gè)時(shí)候能夠正常調(diào)用printC,但實(shí)際上這種做法的結(jié)果是“undefined” // 嘗試過(guò),如果添加一些數(shù)據(jù)成員的運(yùn)算,這個(gè)時(shí)候?qū)?huì)使得運(yùn)算結(jié)果無(wú)法預(yù)測(cè) // 所以,在運(yùn)行時(shí)候該邏輯相關(guān)的行為是不清晰的。 return 0; }
2.2 static_cast對(duì)于自定義類(lèi)型的轉(zhuǎn)換
上面這個(gè)小例子簡(jiǎn)單對(duì)比了static_cast與C-Like在針對(duì)不同繼承體系的類(lèi)之間表現(xiàn)的差異性,現(xiàn)在先把范圍縮小到同一繼承體系當(dāng)中的類(lèi)型轉(zhuǎn)換。(注:這里所說(shuō)的類(lèi)型一般是針對(duì)類(lèi)的指針或者類(lèi)的引用)
static_cast針對(duì)同一繼承體系的類(lèi)之間的轉(zhuǎn)換,它既可以進(jìn)行upcast也可以進(jìn)行downcast。一般來(lái)說(shuō),在進(jìn)行upcast時(shí)是沒(méi)有問(wèn)題的,畢竟子類(lèi)當(dāng)中一定包含有父類(lèi)的相關(guān)操作集合,所以通過(guò)轉(zhuǎn)換之后的指針或者引用來(lái)操作對(duì)應(yīng)的對(duì)象,其行為上是可以保證沒(méi)問(wèn)題。這和使用static_cast與使用C-Like或者直接隱式轉(zhuǎn)換效果一樣(當(dāng)然,其結(jié)果是否符合程序員本身的預(yù)期與當(dāng)時(shí)的設(shè)計(jì)有關(guān)系)。
需要注意的是,使用static_cast進(jìn)行downcast應(yīng)該避免,因?yàn)樗梢皂樌舆^(guò)編譯器的法眼,但在運(yùn)行時(shí)卻會(huì)爆發(fā)未定義的問(wèn)題:
#include <iostream> class A { public: A():i(1), j(1){} ~A(){} void printA() { std::cout <<"call printA() in class A" <<std::endl; } void printSum() { std::cout <<"sum = " <<i+j <<std::endl; } private: int i, j; }; class B : public A { public: B():a(2), b(2) {} ~B(){} void printB() { std::cout <<"call printB() in class B" <<std::endl; } void printSum() { std::cout <<"sum = " <<a+b <<std::endl; } void Add() { a++; b++; } private: double a, b; }; int main() { B *ptrB = new B; ptrB->printSum(); //打印結(jié)果:sum = 4 A *ptrA = static_cast<B *>(ptrB); ptrA->printA(); ptrA->printSum(); //打印結(jié)果:sum = 2 //在進(jìn)行upcast的時(shí)候,指針指向的對(duì)象的行為與指針的類(lèi)型相關(guān)。 ptrA = new A; ptrA->printSum(); //打印結(jié)果:sum = 2 ptrB = static_cast<B *>(ptrA); ptrB->printB(); ptrB->printSum(); //打印結(jié)果:sum = 0 //在進(jìn)行downcast的時(shí)候,其行為是“undefined”。 //B b; //B &rB = b; //rB.printSum(); //打印結(jié)果:sum = 4 //A &rA = static_cast<A &>(rB); //rA.printA(); //rA.printSum(); //打印結(jié)果:sum = 2 //在進(jìn)行upcast的時(shí)候,引用指向的對(duì)象的行為與引用的類(lèi)型相關(guān)。 //A a; //A &rA = a; //rA.printSum(); //打印結(jié)果:sum = 4 //B &rB = static_cast<B &>(rA); //rB.printB(); //rB.printSum(); //打印結(jié)果:sum = 5.18629e-317 //在進(jìn)行downcast的時(shí)候,其行為是“undefined”。 return 0; }
如上,static_cast在對(duì)同一繼承體系的類(lèi)之間進(jìn)行downcast時(shí)的表現(xiàn),與C-Like針對(duì)分屬不同繼承體系的類(lèi)之間進(jìn)行轉(zhuǎn)換時(shí)的表現(xiàn)一樣,將是未定義的。所以,應(yīng)該盡可能使用static_cast執(zhí)行downcast轉(zhuǎn)換,更準(zhǔn)確的說(shuō),應(yīng)該盡可能避免對(duì)集成體系的類(lèi)對(duì)應(yīng)的指針或者引用進(jìn)行downcast轉(zhuǎn)換。
既然這樣,那是不是在軟件開(kāi)發(fā)過(guò)程當(dāng)中就不會(huì)存在downcast的這種情況了呢?實(shí)際上不是的。一般來(lái)說(shuō),進(jìn)行downcast的時(shí)候一般是在虛繼承的場(chǎng)景當(dāng)中,這個(gè)時(shí)候dynamic_cast就上場(chǎng)了。
2.3 dynamic_cast
dynamic_cast的使用主要在downcast的場(chǎng)景,它的使用需要滿(mǎn)足兩個(gè)條件:
downcast時(shí)轉(zhuǎn)換的類(lèi)之間存在著“虛繼承”的關(guān)系
轉(zhuǎn)換之后的類(lèi)型與其指向的實(shí)際類(lèi)型要相符合
dynamic_cast對(duì)于upcast與static_cast的效果是一樣的,然而因?yàn)閐ynamic_cast依賴(lài)于RTTI,所以在性能上面相比static_cast略低。
#include <iostream> #include <exception> class A { public: virtual void print() { std::cout <<"Welcome to WorldA!" <<std::endl; } }; class B : public A { public: B():a(0), b(0) {} ~B(){} virtual void print() { std::cout <<"Welcome to WorldB!" <<std::endl; } private: double a, b; }; int main() { B *ptrB = new B; A *ptrA = dynamic_cast<A *>(ptrB); ptrA->print(); //在虛繼承當(dāng)中,針對(duì)指針執(zhí)行upcast時(shí)dynamic_cast轉(zhuǎn)換的效果與static_cast一樣 //對(duì)是否存在virtual沒(méi)有要求,會(huì)實(shí)際調(diào)用所指向?qū)ο蟮某蓡T。 //A *ptrA = new A; //B *ptrB = dynamic_cast<B *>(ptrA); //ptrB->print(); //Segmentation fault,針對(duì)指針執(zhí)行downcast時(shí)轉(zhuǎn)換不成功,返回NULL。 //A a; //A &ra = a; //B &b = dynamic_cast<B &>(ra); //b.print(); //拋出St8bad_cast異常,針對(duì)引用執(zhí)行downcast時(shí)轉(zhuǎn)換不成功,拋出異常。 //ptrA = new A; //ptrB = static_cast<B *>(ptrA); //ptrB->print(); //使用static_cast進(jìn)行downcast的時(shí)候,與dynamic_cast返回NULL不同, //這里會(huì)調(diào)用ptrB實(shí)際指向的對(duì)象的虛函數(shù)。 //ptrA = new A; //ptrB = dynamic_cast<B *>(ptrA); //ptrB->print(); //在進(jìn)行downcast時(shí),如果沒(méi)有virtual成員,那么在編譯時(shí)會(huì)提示: // In function ‘int main()': // cannot dynamic_cast ‘ptrA' (of type ‘class A*') to type ‘class B*' (source type is not polymorphic) return 0; }
從這個(gè)例子可以看出,在虛繼承場(chǎng)景下,能夠使用dynamic_cast的地方一定可以使用static_cast,然而dynamic_cast卻有著更嚴(yán)格的要求,以便幫助程序員編寫(xiě)出更加嚴(yán)謹(jǐn)?shù)拇a。只不過(guò),它在性能上面多了一部分開(kāi)銷(xiāo)。
3 reinterpret_cast
reinterpret_cast是最危險(xiǎn)的一種cast,之所以說(shuō)它最危險(xiǎn),是因?yàn)樗谋憩F(xiàn)和C-Like一般強(qiáng)大,稍微不注意就會(huì)出現(xiàn)錯(cuò)誤。它一般在一些low-level的轉(zhuǎn)換或者位操作當(dāng)中運(yùn)用。
#include <iostream> class A { public: A(){} ~A(){} void print() { std::cout <<"Hello World!" <<std::endl; } }; class B { public: B():a(0), b(0) {} ~B(){} void call() { std::cout <<"Happy for your call!" <<std::endl; } private: double a, b; }; int main() { //A *ptrA = new A; //B *ptrB = reinterpret_cast<B *>(ptrA); //ptrB->call(); //正常編譯 //A *ptrA = new A; //B *ptrB = (B *)(ptrA); //ptrB->call(); //正常編譯 //A *ptrA = new A; //B *ptrB = static_cast<B *>(ptrA); //ptrB->call(); //編譯不通過(guò),提示: //In function ‘int main()': //error: invalid static_cast from type ‘A*' to type ‘B*' //char c; //char *pC = &c; //int *pInt = static_cast<int *>(pC); //編譯提示錯(cuò)誤:error: invalid static_cast from type ‘char*' to type ‘int*' //int *pInt = reinterpret_cast<int *>(pC); //正常編譯。 //int *pInt = (int *)(pC); //正常編譯。 return 0; }
分析了static_cast,dynamic_cast與reinterpret_cast之后就可以畫(huà)出如下的圖示對(duì)它們之間的區(qū)別進(jìn)行簡(jiǎn)單比較了。這里沒(méi)有將const_cast納入進(jìn)來(lái)是因?yàn)樗容^特殊,另外分節(jié)對(duì)它進(jìn)行介紹。
---------------- / dynamic_cast \ -->同一繼承體系(virtual)的類(lèi)指針或引用[更安全的downcast] ~~~~~~~~~~~~~~~~~~~~ / static_cast \ -->基礎(chǔ)類(lèi)型[更安全],同一繼承體系的類(lèi)指針或引用 ~~~~~~~~~~~~~~~~~~~~~~~~ / reinterpret_cast \ -->與C-Like的作用一致,沒(méi)有任何靜態(tài)或者動(dòng)態(tài)的checking機(jī)制 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ / C-Like \ -->基礎(chǔ)類(lèi)型,同一繼承體系的類(lèi)指針或引用,不同繼承體系類(lèi)的指針或引用 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 const_cast
const_cast能夠使用來(lái)移出或者增加一個(gè)變量的const屬性,最初的時(shí)候我覺(jué)得這個(gè)const_cast比較怪異,C里面一直都沒(méi)有類(lèi)似的東西來(lái)消除const屬性,這里是否會(huì)多余呢?其實(shí),我這種想法本身就沒(méi)根沒(méi)據(jù)。后來(lái)想想,在C++當(dāng)中一直提倡將常量聲明為const,這樣一旦常量變得多了起來(lái),在與其他軟件組件或者第三方庫(kù)進(jìn)行銜接的時(shí)候就難免會(huì)碰到需要cast const屬性的問(wèn)題。比如:
const int myConst = 15; int *nonConst = const_cast<int *>(&myConst); void print(int *p) { std::cout << *p; } print(&myConst); // 編譯錯(cuò)誤:error: invalid conversion from ‘const int*' to ‘int*' print(nonConst); // 正常
不過(guò),在使用const_cast的時(shí)候應(yīng)該要注意,如果沒(méi)有必要盡量不要去修改它的值:
const int myConst = 15; int *nonConst = const_cast<int *>(&myConst); *nonConst = 10; // 如果該變量存放在read-only內(nèi)存區(qū)當(dāng)中,在運(yùn)行時(shí)可能會(huì)出現(xiàn)錯(cuò)誤。
5 小結(jié)
在C++當(dāng)中對(duì)于大部分?jǐn)?shù)據(jù)類(lèi)型而言,使用C-Like的類(lèi)型轉(zhuǎn)換已經(jīng)完全夠用了。然而,不少人一直在倡導(dǎo)進(jìn)行顯式數(shù)據(jù)類(lèi)型轉(zhuǎn)換的時(shí)候盡可能地使用C++規(guī)定的類(lèi)型轉(zhuǎn)換操作。我想這里面大概有兩方面的原因:
第一種,C++是一門(mén)“新”的編程語(yǔ)言,應(yīng)該學(xué)會(huì)用它本身的思想來(lái)解決編程方面的問(wèn)題;
第二種,盡管C-Like轉(zhuǎn)換操作能力強(qiáng)大,但如果將其任意使用,會(huì)產(chǎn)生不少在編譯期間隱藏,卻在運(yùn)行時(shí)候神出鬼沒(méi)。這些問(wèn)題使得軟件的行為極不清晰。
如此,C++當(dāng)中引出了其他四種類(lèi)型轉(zhuǎn)換方式,用來(lái)更加安全的完成一些場(chǎng)合的類(lèi)型轉(zhuǎn)換操作。比如使用reinterpret_cast的時(shí)候會(huì)表示你確定無(wú)疑的想使用C-Like的類(lèi)型轉(zhuǎn)換;在使用static_cast的時(shí)候想要確保轉(zhuǎn)換的對(duì)象基本兼容,比如無(wú)法將char *轉(zhuǎn)換為int *,無(wú)法在不同繼承體系類(lèi)的指針或引用之間進(jìn)行轉(zhuǎn)換;而使用dynamic_cast的時(shí)候是要對(duì)虛繼承下的類(lèi)執(zhí)行downcast轉(zhuǎn)換,并且已經(jīng)明了當(dāng)前性能已經(jīng)不是主要的影響因素......
回答一下前文提到的問(wèn)題??梢赃@么說(shuō),對(duì)于const_cast, static_cast, reinterpret_cast和dynamic_cast所能夠完成的所有轉(zhuǎn)換,C-Like也可以完成。但是,C-Like轉(zhuǎn)換卻沒(méi)有static_cast, dynamic_cast分別提供的編譯時(shí)類(lèi)型檢測(cè)和運(yùn)行時(shí)類(lèi)型檢測(cè)。
C++之父Bjarne Stroustrup博士在這里也談到了他的觀(guān)點(diǎn),主要有兩點(diǎn):其一,C-Like的cast極具破壞性并且在代碼文本上也難得花不少力氣搜索到它;其二,新式的cast使得程序員更有目的使用它們并且讓編譯器能夠發(fā)現(xiàn)更多的錯(cuò)誤;其三,新的cast符合模板聲明規(guī)范,可以讓程序員編寫(xiě)它們自己的cast。
上一篇:詳解C語(yǔ)言中index()函數(shù)和rindex()函數(shù)的用法
欄 目:C語(yǔ)言
下一篇:詳解C語(yǔ)言中strcpy()函數(shù)與strncpy()函數(shù)的使用
本文標(biāo)題:C++中的四種類(lèi)型轉(zhuǎn)換
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/2860.html
您可能感興趣的文章
- 04-02c語(yǔ)言沒(méi)有round函數(shù) round c語(yǔ)言
- 01-10深入理解C++中常見(jiàn)的關(guān)鍵字含義
- 01-10使用C++實(shí)現(xiàn)全排列算法的方法詳解
- 01-10深入Main函數(shù)中的參數(shù)argc,argv的使用詳解
- 01-10c++中inline的用法分析
- 01-10如何尋找數(shù)組中的第二大數(shù)
- 01-10用C++實(shí)現(xiàn)DBSCAN聚類(lèi)算法
- 01-10全排列算法的非遞歸實(shí)現(xiàn)與遞歸實(shí)現(xiàn)的方法(C++)
- 01-10C++大數(shù)模板(推薦)
- 01-10淺談C/C++中的static與extern關(guān)鍵字的使用詳解


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 04-02c語(yǔ)言函數(shù)調(diào)用后清空內(nèi)存 c語(yǔ)言調(diào)用
- 04-02func函數(shù)+在C語(yǔ)言 func函數(shù)在c語(yǔ)言中
- 04-02c語(yǔ)言的正則匹配函數(shù) c語(yǔ)言正則表達(dá)
- 04-02c語(yǔ)言用函數(shù)寫(xiě)分段 用c語(yǔ)言表示分段
- 04-02c語(yǔ)言中對(duì)數(shù)函數(shù)的表達(dá)式 c語(yǔ)言中對(duì)
- 04-02c語(yǔ)言編寫(xiě)函數(shù)冒泡排序 c語(yǔ)言冒泡排
- 04-02c語(yǔ)言沒(méi)有round函數(shù) round c語(yǔ)言
- 04-02c語(yǔ)言分段函數(shù)怎么求 用c語(yǔ)言求分段
- 04-02C語(yǔ)言中怎么打出三角函數(shù) c語(yǔ)言中怎
- 04-02c語(yǔ)言調(diào)用函數(shù)求fibo C語(yǔ)言調(diào)用函數(shù)求
隨機(jī)閱讀
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-10C#中split用法實(shí)例總結(jié)
- 01-10delphi制作wav文件的方法
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 04-02jquery與jsp,用jquery
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什