淺析C++中dynamic_cast和static_cast實(shí)例語法詳解
1. static_cast
1.1 static_cast語法
static_cast< new_type >(expression)
備注:new_type為目標(biāo)數(shù)據(jù)類型,expression為原始數(shù)據(jù)類型變量或者表達(dá)式。
C風(fēng)格寫法:
double scores = 96.5; int n = (int)scores;
C++ 新風(fēng)格的寫法為:
double scores = 96.5; int n = static_cast<int>(scores);
1.2 為什么要有static_cast等
隱式類型轉(zhuǎn)換是安全的,顯式類型轉(zhuǎn)換是有風(fēng)險(xiǎn)的,C語言之所以增加強(qiáng)制類型轉(zhuǎn)換的語法,就是為了強(qiáng)調(diào)風(fēng)險(xiǎn),讓程序員意識(shí)到自己在做什么。
但是,這種強(qiáng)調(diào)風(fēng)險(xiǎn)的方式還是比較粗放,粒度比較大,它并沒有表明存在什么風(fēng)險(xiǎn),風(fēng)險(xiǎn)程度如何。
為了使?jié)撛陲L(fēng)險(xiǎn)更加細(xì)化,使問題追溯更加方便,使書寫格式更加規(guī)范,C++ 對(duì)類型轉(zhuǎn)換進(jìn)行了分類,并新增了四個(gè)關(guān)鍵字來予以支持,它們分別是:
關(guān)鍵字 | 說明 |
---|---|
static_cast | 用于良性轉(zhuǎn)換,一般不會(huì)導(dǎo)致意外發(fā)生,風(fēng)險(xiǎn)很低。 |
const_cast | 用于 const 與非 const、volatile 與非 volatile 之間的轉(zhuǎn)換。 |
reinterpret_cast | 高度危險(xiǎn)的轉(zhuǎn)換,這種轉(zhuǎn)換僅僅是對(duì)二進(jìn)制位的重新解釋,不會(huì)借助已有的轉(zhuǎn)換規(guī)則對(duì)數(shù)據(jù)進(jìn)行調(diào)整,但是可以實(shí)現(xiàn)最靈活的 C++ 類型轉(zhuǎn)換。 |
dynamic_cast | 借助 RTTI,用于類型安全的向下轉(zhuǎn)型(Downcasting)。 |
1.2 static_cast的作用
static_cast相當(dāng)于傳統(tǒng)的C語言里的強(qiáng)制轉(zhuǎn)換,該運(yùn)算符把expression轉(zhuǎn)換為new_type類型,用來強(qiáng)迫隱式轉(zhuǎn)換如non-const對(duì)象轉(zhuǎn)為const對(duì)象,編譯時(shí)檢查,用于非多態(tài)的轉(zhuǎn)換,可以轉(zhuǎn)換指針及其他,但沒有運(yùn)行時(shí)類型檢查來保證轉(zhuǎn)換的安全性。它主要有如下幾種用法:
風(fēng)險(xiǎn)較低的用法:
- 原有的自動(dòng)類型轉(zhuǎn)換,例如 short 轉(zhuǎn) int、int 轉(zhuǎn) double、const 轉(zhuǎn)非 const、向上轉(zhuǎn)型等;
- void 指針和具體類型指針之間的轉(zhuǎn)換,例如
void *
轉(zhuǎn)int *
、char *
轉(zhuǎn)void *
等; - 有轉(zhuǎn)換構(gòu)造函數(shù)或者類型轉(zhuǎn)換函數(shù)的類與其它類型之間的轉(zhuǎn)換,例如 double 轉(zhuǎn) Complex(調(diào)用轉(zhuǎn)換構(gòu)造函數(shù))、Complex 轉(zhuǎn) double(調(diào)用類型轉(zhuǎn)換函數(shù))。
需要注意的是,static_cast 不能用于無關(guān)類型之間的轉(zhuǎn)換,因?yàn)檫@些轉(zhuǎn)換都是有風(fēng)險(xiǎn)的,例如:
- 兩個(gè)具體類型指針之間的轉(zhuǎn)換,例如
int *
轉(zhuǎn)double *
、Student *
轉(zhuǎn)int *
等。不同類型的數(shù)據(jù)存儲(chǔ)格式不一樣,長(zhǎng)度也不一樣,用 A 類型的指針指向 B 類型的數(shù)據(jù)后,會(huì)按照 A 類型的方式來處理數(shù)據(jù):如果是讀取操作,可能會(huì)得到一堆沒有意義的值;如果是寫入操作,可能會(huì)使 B 類型的數(shù)據(jù)遭到破壞,當(dāng)再次以 B 類型的方式讀取數(shù)據(jù)時(shí)會(huì)得到一堆沒有意義的值。 - int 和指針之間的轉(zhuǎn)換。將一個(gè)具體的地址賦值給指針變量是非常危險(xiǎn)的,因?yàn)樵摰刂飞系膬?nèi)存可能沒有分配,也可能沒有讀寫權(quán)限,恰好是可用內(nèi)存反而是小概率事件。
1.3 static_cast用法
#include <iostream> #include <cstdlib> using namespace std; class Complex{ public: Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ } public: operator double() const { return m_real; } //類型轉(zhuǎn)換函數(shù) private: double m_real; double m_imag; }; int main(){ //下面是正確的用法 int m = 100; Complex c(12.5, 23.8); long n = static_cast<long>(m); //寬轉(zhuǎn)換,沒有信息丟失 char ch = static_cast<char>(m); //窄轉(zhuǎn)換,可能會(huì)丟失信息 int *p1 = static_cast<int*>( malloc(10 * sizeof(int)) ); //將void指針轉(zhuǎn)換為具體類型指針 void *p2 = static_cast<void*>(p1); //將具體類型指針,轉(zhuǎn)換為void指針 double real= static_cast<double>(c); //調(diào)用類型轉(zhuǎn)換函數(shù) //下面的用法是錯(cuò)誤的 float *p3 = static_cast<float*>(p1); //不能在兩個(gè)具體類型的指針之間進(jìn)行轉(zhuǎn)換 p3 = static_cast<float*>(0X2DF9); //不能將整數(shù)轉(zhuǎn)換為指針類型 return 0; }
2. dynamic_cast
2.1 dynamic_cast 語法
dynamic_cast <newType> (expression)
newType 和 expression 必須同時(shí)是指針類型或者引用類型。換句話說,dynamic_cast 只能轉(zhuǎn)換指針類型和引用類型,其它類型(int、double、數(shù)組、類、結(jié)構(gòu)體等)都不行。
對(duì)于指針,如果轉(zhuǎn)換失敗將返回 NULL;對(duì)于引用,如果轉(zhuǎn)換失敗將拋出std::bad_cast
異常。
2.2 dynamic_cast 用法
dynamic_cast 用于在類的繼承層次之間進(jìn)行類型轉(zhuǎn)換,它既允許向上轉(zhuǎn)型(Upcasting),也允許向下轉(zhuǎn)型(Downcasting)。向上轉(zhuǎn)型是無條件的,不會(huì)進(jìn)行任何檢測(cè),所以都能成功;向下轉(zhuǎn)型的前提必須是安全的,要借助 RTTI 進(jìn)行檢測(cè),所有只有一部分能成功。
dynamic_cast 與 static_cast 是相對(duì)的,dynamic_cast 是“動(dòng)態(tài)轉(zhuǎn)換”的意思,static_cast 是“靜態(tài)轉(zhuǎn)換”的意思。dynamic_cast 會(huì)在程序運(yùn)行期間借助 RTTI 進(jìn)行類型轉(zhuǎn)換,這就要求基類必須包含虛函數(shù);static_cast 在編譯期間完成類型轉(zhuǎn)換,能夠更加及時(shí)地發(fā)現(xiàn)錯(cuò)誤。
2.3 dynamic_cast 實(shí)例
2.3.1 向上轉(zhuǎn)型(Upcasting)
向上轉(zhuǎn)型時(shí),只要待轉(zhuǎn)換的兩個(gè)類型之間存在繼承關(guān)系,并且基類包含了虛函數(shù)(這些信息在編譯期間就能確定),就一定能轉(zhuǎn)換成功。因?yàn)橄蛏限D(zhuǎn)型始終是安全的,所以 dynamic_cast 不會(huì)進(jìn)行任何運(yùn)行期間的檢查,這個(gè)時(shí)候的 dynamic_cast 和 static_cast 就沒有什么區(qū)別了。
「向上轉(zhuǎn)型時(shí)不執(zhí)行運(yùn)行期檢測(cè)」雖然提高了效率,但也留下了安全隱患,請(qǐng)看下面的代碼:
#include <iostream> #include <iomanip> using namespace std; class Base{ public: Base(int a = 0): m_a(a){ } int get_a() const{ return m_a; } virtual void func() const { } protected: int m_a; }; class Derived: public Base{ public: Derived(int a = 0, int b = 0): Base(a), m_b(b){ } int get_b() const { return m_b; } private: int m_b; }; int main(){ //情況① Derived *pd1 = new Derived(35, 78); Base *pb1 = dynamic_cast<Derived*>(pd1); cout<<"pd1 = "<<pd1<<", pb1 = "<<pb1<<endl; cout<<pb1->get_a()<<endl; pb1->func(); //情況② int n = 100; Derived *pd2 = reinterpret_cast<Derived*>(&n); Base *pb2 = dynamic_cast<Base*>(pd2); cout<<"pd2 = "<<pd2<<", pb2 = "<<pb2<<endl; cout<<pb2->get_a()<<endl; //輸出一個(gè)垃圾值 pb2->func(); //內(nèi)存錯(cuò)誤 return 0; }
運(yùn)行結(jié)果如下
可以看到pd1與pb1的地址相同,且pb1可以正常調(diào)用Base類的方法
對(duì)于情況②
pd 2指向的是整型變量 n,并沒有指向一個(gè) Derived 類的對(duì)象,在使用 dynamic_cast 進(jìn)行類型轉(zhuǎn)換時(shí)也沒有檢查這一點(diǎn)(因?yàn)橄蛏限D(zhuǎn)型始終是安全的,所以 dynamic_cast 不會(huì)進(jìn)行任何運(yùn)行期間的檢查)
而是將 pd 的值直接賦給了 pb(這里并不需要調(diào)整偏移量),最終導(dǎo)致 pb 也指向了 n。因?yàn)?pb 指向的不是一個(gè)對(duì)象,所以get_a()
得不到 m_a 的值(實(shí)際上得到的是一個(gè)垃圾值),pb2->func()
也得不到 func() 函數(shù)的正確地址。
運(yùn)行結(jié)果如下
簡(jiǎn)單來說就是向上轉(zhuǎn)型是不檢查的,所以大家得知道自己在做什么,不能隨意的轉(zhuǎn)換
2.3.2 向下轉(zhuǎn)型(Downcasting)
向下轉(zhuǎn)型是有風(fēng)險(xiǎn)的,dynamic_cast 會(huì)借助 RTTI 信息進(jìn)行檢測(cè),確定安全的才能轉(zhuǎn)換成功,否則就轉(zhuǎn)換失敗。
下面看一個(gè)例子
#include <iostream> using namespace std; class A{ public: virtual void func() const { cout<<"Class A"<<endl; } private: int m_a; }; class B: public A{ public: virtual void func() const { cout<<"Class B"<<endl; } private: int m_b; }; class C: public B{ public: virtual void func() const { cout<<"Class C"<<endl; } private: int m_c; }; class D: public C{ public: virtual void func() const { cout<<"Class D"<<endl; } private: int m_d; }; int main(){ A *pa = new A(); B *pb; C *pc; //情況① pb = dynamic_cast<B*>(pa); //向下轉(zhuǎn)型失敗 if(pb == NULL){ cout<<"Downcasting failed: A* to B*"<<endl; }else{ cout<<"Downcasting successfully: A* to B*"<<endl; pb -> func(); } pc = dynamic_cast<C*>(pa); //向下轉(zhuǎn)型失敗 if(pc == NULL){ cout<<"Downcasting failed: A* to C*"<<endl; }else{ cout<<"Downcasting successfully: A* to C*"<<endl; pc -> func(); } cout<<"-------------------------"<<endl; //情況② pa = new D(); //向上轉(zhuǎn)型都是允許的 pb = dynamic_cast<B*>(pa); //向下轉(zhuǎn)型成功 if(pb == NULL){ cout<<"Downcasting failed: A* to B*"<<endl; }else{ cout<<"Downcasting successfully: A* to B*"<<endl; pb -> func(); } pc = dynamic_cast<C*>(pa); //向下轉(zhuǎn)型成功 if(pc == NULL){ cout<<"Downcasting failed: A* to C*"<<endl; }else{ cout<<"Downcasting successfully: A* to C*"<<endl; pc -> func(); } return 0; }
運(yùn)行結(jié)果
可以看到,前兩次轉(zhuǎn)換失敗,但是后兩次轉(zhuǎn)換成功
這段代碼中類的繼承順序?yàn)椋篈 --> B --> C --> D。pa 是A*
類型的指針,當(dāng) pa 指向 A 類型的對(duì)象時(shí),向下轉(zhuǎn)型失敗,pa 不能轉(zhuǎn)換為B*
或C*
類型。當(dāng) pa 指向 D 類型的對(duì)象時(shí),向下轉(zhuǎn)型成功,pa 可以轉(zhuǎn)換為B*
或C*
類型。同樣都是向下轉(zhuǎn)型,為什么 pa 指向的對(duì)象不同,轉(zhuǎn)換的結(jié)果就大相徑庭呢?
因?yàn)槊總€(gè)類都會(huì)在內(nèi)存中保存一份類型信息,編譯器會(huì)將存在繼承關(guān)系的類的類型信息使用指針“連接”起來,從而形成一個(gè)繼承鏈(Inheritance Chain),也就是如下圖所示的樣子:
當(dāng)使用 dynamic_cast 對(duì)指針進(jìn)行類型轉(zhuǎn)換時(shí),程序會(huì)先找到該指針指向的對(duì)象,再根據(jù)對(duì)象找到當(dāng)前類(指針指向的對(duì)象所屬的類)的類型信息,并從此節(jié)點(diǎn)開始沿著繼承鏈向上遍歷,如果找到了要轉(zhuǎn)化的目標(biāo)類型,那么說明這種轉(zhuǎn)換是安全的,就能夠轉(zhuǎn)換成功,如果沒有找到要轉(zhuǎn)換的目標(biāo)類型,那么說明這種轉(zhuǎn)換存在較大的風(fēng)險(xiǎn),就不能轉(zhuǎn)換。
所以在第二種方式中,pa實(shí)際上是指向的D,于是程序順著D開始向上找,找到了B和C,于是認(rèn)定是安全的,所以轉(zhuǎn)換成功
總起來說,dynamic_cast 會(huì)在程序運(yùn)行過程中遍歷繼承鏈,如果途中遇到了要轉(zhuǎn)換的目標(biāo)類型,那么就能夠轉(zhuǎn)換成功,如果直到繼承鏈的頂點(diǎn)(最頂層的基類)還沒有遇到要轉(zhuǎn)換的目標(biāo)類型,那么就轉(zhuǎn)換失敗。對(duì)于同一個(gè)指針(例如 pa),它指向的對(duì)象不同,會(huì)導(dǎo)致遍歷繼承鏈的起點(diǎn)不一樣,途中能夠匹配到的類型也不一樣,所以相同的類型轉(zhuǎn)換產(chǎn)生了不同的結(jié)果。
3. 參考鏈接
http://c.biancheng.net/cpp/biancheng/view/3297.html
https://blog.csdn.net/u014624623/article/details/79837849
https://www.cnblogs.com/wanghongyang/ 【本文博客】
到此這篇關(guān)于淺析C++中dynamic_cast和static_cast實(shí)例演示的文章就介紹到這了,更多相關(guān)C++中dynamic_cast與static_cast內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C++強(qiáng)制類型轉(zhuǎn)換(static_cast、dynamic_cast、const_cast、reinterpret_cast)
- C++中的new/delete、構(gòu)造/析構(gòu)函數(shù)、dynamic_cast分析
- 由static_cast和dynamic_cast到C++對(duì)象占用內(nèi)存的全面分析
- C++中的類型轉(zhuǎn)換static_cast、dynamic_cast、const_cast和reinterpret_cast總結(jié)
- c++ dynamic_cast與static_cast使用方法示例
- C++ 中dynamic_cast<>的使用方法小結(jié)
- C++的dynamic示例代碼詳解
相關(guān)文章
C++調(diào)用Python基礎(chǔ)功能實(shí)例詳解
c++調(diào)用Python首先安裝Python,本文以win7為例,給大家詳細(xì)介紹C++調(diào)用Python基礎(chǔ)功能,需要的朋友參考下吧2017-04-04VC++ 中ListCtrl經(jīng)驗(yàn)總結(jié)
這篇文章主要介紹了VC++ 中ListCtrl經(jīng)驗(yàn)總結(jié)的相關(guān)資料,需要的朋友可以參考下2015-06-06C++?BoostAsyncSocket實(shí)現(xiàn)異步反彈通信的案例詳解
這篇文章主要為大家詳細(xì)介紹了C++?BoostAsyncSocket如何實(shí)現(xiàn)異步反彈通信,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的可以了解一下2023-03-03Qt串口通信開發(fā)之Qt串口通信模塊QSerialPort開發(fā)完整實(shí)例(串口助手開發(fā))
這篇文章主要介紹了Qt串口通信開發(fā)之Qt串口通信模塊QSerialPort開發(fā)完整實(shí)例(串口助手開發(fā)),需要的朋友可以參考下2020-03-03C++設(shè)計(jì)一個(gè)簡(jiǎn)單內(nèi)存池的全過程
利用C/C++開發(fā)大型應(yīng)用程序中,內(nèi)存的管理與分配是一個(gè)需要認(rèn)真考慮的部分,下面這篇文章主要給大家介紹了關(guān)于C++設(shè)計(jì)一個(gè)簡(jiǎn)單內(nèi)存池的全過程,需要的朋友可以參考下2021-09-09深入理解C++模板如何實(shí)現(xiàn)多態(tài)思想
這篇文章主要為大家詳細(xì)介紹了C++模板如何實(shí)現(xiàn)多態(tài)的相關(guān)資料,文中的示例代碼講解詳細(xì),對(duì)我們深入了解C++有一定的幫助,感興趣的可以了解一下2023-03-03利用C語言模擬實(shí)現(xiàn)qsort,strcpy,strcat,strcmp函數(shù)
這篇文章主要為大家詳細(xì)介紹了如何通過C語言模擬實(shí)現(xiàn)qsort(采用冒泡的方式),strcpy,strcat,strcmp等函數(shù),文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-11-11