1. static_cast
1.1 static_cast文法
static_cast< new_type >(expression)
備注:new_type為目标資料類型,expression為原始資料類型變量或者表達式。
C風格寫法:
double scores = 96.5;
int n = (int)scores;
C++ 新風格的寫法為:
double scores = 96.5;
int n = static_cast<int>(scores);
1.2 為什麼要有static_cast等
隐式類型轉換是安全的,顯式類型轉換是有風險的,C語言之是以增加強制類型轉換的文法,就是為了強調風險,讓程式員意識到自己在做什麼。
但是,這種強調風險的方式還是比較粗放,粒度比較大,它并沒有表明存在什麼風險,風險程度如何。
為了使潛在風險更加細化,使問題追溯更加友善,使書寫格式更加規範,C++ 對類型轉換進行了分類,并新增了四個關鍵字來予以支援,它們分别是:
關鍵字 | 說明 |
---|---|
static_cast | 用于良性轉換,一般不會導緻意外發生,風險很低。 |
const_cast | 用于 const 與非 const、volatile 與非 volatile 之間的轉換。 |
reinterpret_cast | 高度危險的轉換,這種轉換僅僅是對二進制位的重新解釋,不會借助已有的轉換規則對資料進行調整,但是可以實作最靈活的 C++ 類型轉換。 |
dynamic_cast | 借助 RTTI,用于類型安全的向下轉型(Downcasting)。 |
1.2 static_cast的作用
static_cast相當于傳統的C語言裡的強制轉換,該運算符把expression轉換為new_type類型,用來強迫隐式轉換如non-const對象轉為const對象,編譯時檢查,用于非多态的轉換,可以轉換指針及其他,但沒有運作時類型檢查來保證轉換的安全性。它主要有如下幾種用法:
風險較低的用法:
- 原有的自動類型轉換,例如 short 轉 int、int 轉 double、const 轉非 const、向上轉型等;
- void 指針和具體類型指針之間的轉換,例如
轉void *
、int *
轉char *
等;void *
- 有轉換構造函數或者類型轉換函數的類與其它類型之間的轉換,例如 double 轉 Complex(調用轉換構造函數)、Complex 轉 double(調用類型轉換函數)。
需要注意的是,static_cast 不能用于無關類型之間的轉換,因為這些轉換都是有風險的,例如:
- 兩個具體類型指針之間的轉換,例如
轉int *
、double *
轉Student *
等。不同類型的資料存儲格式不一樣,長度也不一樣,用 A 類型的指針指向 B 類型的資料後,會按照 A 類型的方式來處理資料:如果是讀取操作,可能會得到一堆沒有意義的值;如果是寫入操作,可能會使 B 類型的資料遭到破壞,當再次以 B 類型的方式讀取資料時會得到一堆沒有意義的值。int *
- int 和指針之間的轉換。将一個具體的位址指派給指針變量是非常危險的,因為該位址上的記憶體可能沒有配置設定,也可能沒有讀寫權限,恰好是可用記憶體反而是小機率事件。
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; } //類型轉換函數
private:
double m_real;
double m_imag;
};
int main(){
//下面是正确的用法
int m = 100;
Complex c(12.5, 23.8);
long n = static_cast<long>(m); //寬轉換,沒有資訊丢失
char ch = static_cast<char>(m); //窄轉換,可能會丢失資訊
int *p1 = static_cast<int*>( malloc(10 * sizeof(int)) ); //将void指針轉換為具體類型指針
void *p2 = static_cast<void*>(p1); //将具體類型指針,轉換為void指針
double real= static_cast<double>(c); //調用類型轉換函數
//下面的用法是錯誤的
float *p3 = static_cast<float*>(p1); //不能在兩個具體類型的指針之間進行轉換
p3 = static_cast<float*>(0X2DF9); //不能将整數轉換為指針類型
return 0;
}
2. dynamic_cast
2.1 dynamic_cast 文法
dynamic_cast <newType> (expression)
newType 和 expression 必須同時是指針類型或者引用類型。換句話說,dynamic_cast 隻能轉換指針類型和引用類型,其它類型(int、double、數組、類、結構體等)都不行。
對于指針,如果轉換失敗将傳回 NULL;對于引用,如果轉換失敗将抛出
std::bad_cast
異常。
2.2 dynamic_cast 用法
dynamic_cast 用于在類的繼承層次之間進行類型轉換,它既允許向上轉型(Upcasting),也允許向下轉型(Downcasting)。向上轉型是無條件的,不會進行任何檢測,是以都能成功;向下轉型的前提必須是安全的,要借助 RTTI 進行檢測,所有隻有一部分能成功。
dynamic_cast 與 static_cast 是相對的,dynamic_cast 是“動态轉換”的意思,static_cast 是“靜态轉換”的意思。dynamic_cast 會在程式運作期間借助 RTTI 進行類型轉換,這就要求基類必須包含虛函數;static_cast 在編譯期間完成類型轉換,能夠更加及時地發現錯誤。
2.3 dynamic_cast 執行個體
2.3.1 向上轉型(Upcasting)
向上轉型時,隻要待轉換的兩個類型之間存在繼承關系,并且基類包含了虛函數(這些資訊在編譯期間就能确定),就一定能轉換成功。因為向上轉型始終是安全的,是以 dynamic_cast 不會進行任何運作期間的檢查,這個時候的 dynamic_cast 和 static_cast 就沒有什麼差別了。
「向上轉型時不執行運作期檢測」雖然提高了效率,但也留下了安全隐患,請看下面的代碼:
#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; //輸出一個垃圾值
pb2->func(); //記憶體錯誤
return 0;
}
運作結果如下

可以看到pd1與pb1的位址相同,且pb1可以正常調用Base類的方法
對于情況②
pd 2指向的是整型變量 n,并沒有指向一個 Derived 類的對象,在使用 dynamic_cast 進行類型轉換時也沒有檢查這一點(因為向上轉型始終是安全的,是以 dynamic_cast 不會進行任何運作期間的檢查)
而是将 pd 的值直接賦給了 pb(這裡并不需要調整偏移量),最終導緻 pb 也指向了 n。因為 pb 指向的不是一個對象,是以
get_a()
得不到 m_a 的值(實際上得到的是一個垃圾值),
pb2->func()
也得不到 func() 函數的正确位址。
運作結果如下
簡單來說就是向上轉型是不檢查的,是以大家得知道自己在做什麼,不能随意的轉換
2.3.2 向下轉型(Downcasting)
向下轉型是有風險的,dynamic_cast 會借助 RTTI 資訊進行檢測,确定安全的才能轉換成功,否則就轉換失敗。
下面看一個例子
#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); //向下轉型失敗
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); //向下轉型失敗
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(); //向上轉型都是允許的
pb = dynamic_cast<B*>(pa); //向下轉型成功
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); //向下轉型成功
if(pc == NULL){
cout<<"Downcasting failed: A* to C*"<<endl;
}else{
cout<<"Downcasting successfully: A* to C*"<<endl;
pc -> func();
}
return 0;
}
運作結果
可以看到,前兩次轉換失敗,但是後兩次轉換成功
這段代碼中類的繼承順序為:A --> B --> C --> D。pa 是
A*
類型的指針,當 pa 指向 A 類型的對象時,向下轉型失敗,pa 不能轉換為
B*
或
C*
類型。當 pa 指向 D 類型的對象時,向下轉型成功,pa 可以轉換為
B*
或
C*
類型。同樣都是向下轉型,為什麼 pa 指向的對象不同,轉換的結果就大相徑庭呢?
因為每個類都會在記憶體中儲存一份類型資訊,編譯器會将存在繼承關系的類的類型資訊使用指針“連接配接”起來,進而形成一個繼承鍊(Inheritance Chain),也就是如下圖所示的樣子:
當使用 dynamic_cast 對指針進行類型轉換時,程式會先找到該指針指向的對象,再根據對象找到目前類(指針指向的對象所屬的類)的類型資訊,并從此節點開始沿着繼承鍊向上周遊,如果找到了要轉化的目标類型,那麼說明這種轉換是安全的,就能夠轉換成功,如果沒有找到要轉換的目标類型,那麼說明這種轉換存在較大的風險,就不能轉換。
是以在第二種方式中,pa實際上是指向的D,于是程式順着D開始向上找,找到了B和C,于是認定是安全的,是以轉換成功
總起來說,dynamic_cast 會在程式運作過程中周遊繼承鍊,如果途中遇到了要轉換的目标類型,那麼就能夠轉換成功,如果直到繼承鍊的頂點(最頂層的基類)還沒有遇到要轉換的目标類型,那麼就轉換失敗。對于同一個指針(例如 pa),它指向的對象不同,會導緻周遊繼承鍊的起點不一樣,途中能夠比對到的類型也不一樣,是以相同的類型轉換産生了不同的結果。
3. 參考連結
http://c.biancheng.net/cpp/biancheng/view/3297.html
https://blog.csdn.net/u014624623/article/details/79837849
https://www.cnblogs.com/wanghongyang/ 【本文部落格】