C++中的四品種型轉換。本站提示廣大學習愛好者:(C++中的四品種型轉換)文章只能為提供參考,不一定能成為您想要的結果。以下是C++中的四品種型轉換正文
1 引子
這篇筆記是依據StackOverflow下面的一個成績整頓而成,重要內容是對C/C++傍邊四品種型轉換操作停止舉例解釋。在之前其實對它們都是有所懂得的,而跟著本身在停止總結,並敲了一些測試示例代碼停止驗證以後,對它們的懂得又深入了一些。
總所周知,在C++ 傍邊引入了四種新的類型轉換操作符:static_cast, dynamic_cast, reinterpret_cast,還有const_cast。就本身見過的一些C++代碼傍邊,它們的應用其實其實不廣泛。很多法式員仍然樂於去應用C-like的類型轉換,由於它壯大且編寫起來又簡略。聽說C-Like類型轉換操作符的感化現實上曾經包含了static_cast, const_cast和reinterpret_cast三種操作符,你信任嗎?一路來著看。
注:下面提到的C-Like類型轉換操作有以下的兩種情勢,這一點年夜家必定都不會生疏。
(new-type) expression new-type (expression)
2 static_cast vs dynamic_cast
之所以把static_cast與dynamic_cast兩兄弟放在一路是由於它們二者比較起來更輕易記得住。起首,從稱號下面它們就有語義絕對的關系,一“靜”一“動”。別的,在功效下面也在必定水平上表現了這一比較的特征,如dynamic_cast的Run-time Checkingt,static_cast在編譯時增長的類型檢測。簡略而言:
static_cast: 1)完成基本數據類型,2)統一個繼續系統中類型的轉換
dynamic_cast:應用多態的場景,增長了一層對真實挪用對象類型的檢討
2.1 從C-Like到static_cast
static_cast關於基本類型如int, float, char和基本類型對應指針的處置年夜多情形下恰如C-Like的轉換一樣,不外static_cast會來得加倍平安。
char c = 10; // 1 個字節 int *p = (int *)&c; // 4 個字節(32bit platform) *p = 5; // 內存踩髒 int *q = static_cast<int *>(&c); // 應用static_cast可在編譯階段將該毛病檢討出來。
關於自界說類型的處置,比擬C-Like而言,它也多了一層掩護,也就是它不支撐在不屬於統一繼續系統的類型之間停止轉換。然則C-Like便可以辦到,看上面這個例子:
#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); // 編譯沒法經由過程,提醒: // In function ‘int main()': // error: invalid static_cast from type ‘A*' to type ‘C*' C *ptrC = (C *)(ptrA); ptrC->printC(); // 編譯正常經由過程。 // 雖然這個時刻可以或許正常挪用printC,但現實上這類做法的成果是“undefined” // 測驗考試過,假如添加一些數據成員的運算,這個時刻將會使得運算成果沒法猜測 // 所以,在運轉時刻該邏輯相干的行動是不清楚的。 return 0; }
2.2 static_cast關於自界說類型的轉換
下面這個小例子簡略比較了static_cast與C-Like在針對分歧繼續系統的類之間表示的差別性,如今先把規模減少到統一繼續系統傍邊的類型轉換。(注:這裡所說的類型普通是針對類的指針或許類的援用)
static_cast針對統一繼續系統的類之間的轉換,它既可以停止upcast也能夠停止downcast。普通來講,在停止upcast時是沒有成績的,究竟子類傍邊必定包括有父類的相干操作聚集,所以經由過程轉換以後的指針或許援用來操尴尬刁難應的對象,其行動上是可以包管沒成績。這和應用static_cast與應用C-Like或許直接隱式轉換後果一樣(固然,其成果能否相符法式員自己的預期與其時的設計有關系)。
須要留意的是,應用static_cast停止downcast應當防止,由於它可以順遂逃過編譯器的高眼,但在運轉時卻會迸發不決義的成績:
#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(); //打印成果:sum = 4 A *ptrA = static_cast<B *>(ptrB); ptrA->printA(); ptrA->printSum(); //打印成果:sum = 2 //在停止upcast的時刻,指針指向的對象的行動與指針的類型相干。 ptrA = new A; ptrA->printSum(); //打印成果:sum = 2 ptrB = static_cast<B *>(ptrA); ptrB->printB(); ptrB->printSum(); //打印成果:sum = 0 //在停止downcast的時刻,其行動是“undefined”。 //B b; //B &rB = b; //rB.printSum(); //打印成果:sum = 4 //A &rA = static_cast<A &>(rB); //rA.printA(); //rA.printSum(); //打印成果:sum = 2 //在停止upcast的時刻,援用指向的對象的行動與援用的類型相干。 //A a; //A &rA = a; //rA.printSum(); //打印成果:sum = 4 //B &rB = static_cast<B &>(rA); //rB.printB(); //rB.printSum(); //打印成果:sum = 5.18629e-317 //在停止downcast的時刻,其行動是“undefined”。 return 0; }
如上,static_cast在對統一繼續系統的類之間停止downcast時的表示,與C-Like針對分屬分歧繼續系統的類之間停止轉換時的表示一樣,將是不決義的。所以,應當盡量應用static_cast履行downcast轉換,更精確的說,應當盡量防止對集成系統的類對應的指針或許援用停止downcast轉換。
既然如許,那是否是在軟件開辟進程傍邊就不會存在downcast的這類情形了呢?現實上不是的。普通來講,停止downcast的時刻普通是在虛繼續的場景傍邊,這個時刻dynamic_cast就上場了。
2.3 dynamic_cast
dynamic_cast的應用重要在downcast的場景,它的應用須要知足兩個前提:
downcast時轉換的類之間存在著“虛繼續”的關系
轉換以後的類型與其指向的現實類型要符合合
dynamic_cast關於upcast與static_cast的後果是一樣的,但是由於dynamic_cast依附於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(); //在虛繼續傍邊,針對指針履行upcast時dynamic_cast轉換的後果與static_cast一樣 //對能否存在virtual沒有請求,會現實挪用所指向對象的成員。 //A *ptrA = new A; //B *ptrB = dynamic_cast<B *>(ptrA); //ptrB->print(); //Segmentation fault,針對指針履行downcast時轉換不勝利,前往NULL。 //A a; //A &ra = a; //B &b = dynamic_cast<B &>(ra); //b.print(); //拋出St8bad_cast異常,針對援用履行downcast時轉換不勝利,拋出異常。 //ptrA = new A; //ptrB = static_cast<B *>(ptrA); //ptrB->print(); //應用static_cast停止downcast的時刻,與dynamic_cast前往NULL分歧, //這裡會挪用ptrB現實指向的對象的虛函數。 //ptrA = new A; //ptrB = dynamic_cast<B *>(ptrA); //ptrB->print(); //在停止downcast時,假如沒有virtual成員,那末在編譯時會提醒: // In function ‘int main()': // cannot dynamic_cast ‘ptrA' (of type ‘class A*') to type ‘class B*' (source type is not polymorphic) return 0; }
從這個例子可以看出,在虛繼續場景下,可以或許應用dynamic_cast的處所必定可使用static_cast,但是dynamic_cast卻有著更嚴厲的請求,以便贊助法式員編寫出加倍嚴謹的代碼。只不外,它在機能下面多了一部門開支。
3 reinterpret_cast
reinterpret_cast是最風險的一種cast,之所以說它最風險,是由於它的表示和C-Like普通壯大,略微不留意就會湧現毛病。它普通在一些low-level的轉換或許位操作傍邊應用。
#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(); //編譯欠亨過,提醒: //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); //編譯提醒毛病: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以後便可以畫出以下的圖示對它們之間的差別停止簡略比擬了。這裡未將const_cast歸入出去是由於它比擬特別,別的分節對它停止引見。
---------------- / dynamic_cast \ -->統一繼續系統(virtual)的類指針或援用[更平安的downcast] ~~~~~~~~~~~~~~~~~~~~ / static_cast \ -->基本類型[更平安],統一繼續系統的類指針或援用 ~~~~~~~~~~~~~~~~~~~~~~~~ / reinterpret_cast \ -->與C-Like的感化分歧,沒有任何靜態或許靜態的checking機制 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ / C-Like \ -->基本類型,統一繼續系統的類指針或援用,分歧繼續系統類的指針或援用 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 const_cast
const_cast可以或許應用來移出或許增長一個變量的const屬性,最後的時刻我認為這個const_cast比擬奇異,C外面一向都沒有相似的器械來清除const屬性,這裡能否會過剩呢?其實,我這類設法主意自己就沒根沒據。後來想一想,在C++傍邊一向倡導將常量聲明為const,如許一旦常質變很多了起來,在與其他軟件組件或許第三方庫停止連接的時刻就不免會碰著須要cast const屬性的成績。好比:
const int myConst = 15; int *nonConst = const_cast<int *>(&myConst); void print(int *p) { std::cout << *p; } print(&myConst); // 編譯毛病:error: invalid conversion from ‘const int*' to ‘int*' print(nonConst); // 正常
不外,在應用const_cast的時刻應當要留意,假如沒有需要盡可能不要去修正它的值:
const int myConst = 15; int *nonConst = const_cast<int *>(&myConst); *nonConst = 10; // 假如該變量寄存在read-only內存區傍邊,在運轉時能夠會湧現毛病。
5 小結
在C++傍邊關於年夜部門數據類型而言,應用C-Like的類型轉換曾經完整夠用了。但是,很多人一向在提倡停止顯式數據類型轉換的時刻盡量地應用C++劃定的類型轉換操作。我想這外面年夜概有兩方面的緣由:
第一種,C++是一門“新”的編程說話,應當學會用它自己的思惟來處理編程方面的成績;
第二種,雖然C-Like轉換操作才能壯大,但假如將其隨意率性應用,會發生很多在編譯時代隱蔽,卻在運轉時刻出沒無常。這些成績使得軟件的行動極不清楚。
如斯,C++傍邊引出了其他四品種型轉換方法,用來加倍平安的完成一些場所的類型轉換操作。好比應用reinterpret_cast的時刻會表現你肯定無疑的想應用C-Like的類型轉換;在應用static_cast的時刻想要確保轉換的對象根本兼容,好比沒法將char *轉換為int *,沒法在分歧繼續系統類的指針或援用之間停止轉換;而應用dynamic_cast的時刻是要對虛繼續下的類履行downcast轉換,而且曾經清楚明了以後機能曾經不是重要的影響身分......
答復一下前文提到的成績。可以這麼說,關於const_cast, static_cast, reinterpret_cast和dynamic_cast所可以或許完成的一切轉換,C-Like也能夠完成。然則,C-Like轉換卻沒有static_cast, dynamic_cast分離供給的編譯時類型檢測和運轉時類型檢測。
C++之父Bjarne Stroustrup博士在這裡也談到了他的不雅點,重要有兩點:其一,C-Like的cast極具損壞性而且在代碼文本上也可貴花很多力量搜刮到它;其二,舊式的cast使得法式員更有目標應用它們而且讓編譯器可以或許發明更多的毛病;其三,新的cast相符模板聲明標准,可讓法式員編寫它們本身的cast。