C++ 既支持 C 風格的類型轉換,又有自己風格的類型轉換。C 風格的轉換格式很簡單,但是有不少缺點:
- 轉換太過隨意,可以在任意類型之間轉換。你可以把一個指向 const 對象的指針轉換成指向非 const 對象的指針,把一個指向基類對象的指針轉換成一個派生類對象的指針,這些轉換之間的差距是非常巨大的,但是傳統的C語言風格的類型轉換沒有區分這些。
- C 風格的轉換沒有統一的關鍵字和標示符。對於大型系統,做代碼排查時容易遺漏和忽略。
C++ 風格完美的解決了上面兩個問題。
- 對類型轉換做了細分,提供了四種不同類型轉換,以支持不同需求的轉換;
- 類型轉換有了統一的標示符,利於代碼排查和檢視。
下面分別來介紹這四種轉換:static_cast、dynamic_cast、const_cast、reinterpert_cast,它們都是類模板。
格式均為:
xx_cast<type-id>(expression),其中type-id-要轉換成什么類型,expression-被轉換類型的目標變量
一、static_case(靜態轉換)
(1)使用場景
- 在基本數據類型之間轉換,如把 int 轉換為 char,這種帶來安全性問題由程序員來保證;
- 在有類型指針與 void * 之間轉換;
- 用於類層次結構中基類和派生類之間指針或引用的轉換。
- 上行轉換(派生類---->基類)是安全的;
- 下行轉換(基類---->派生類)由於沒有動態類型檢查,所以是不安全的。
(2)使用特點
- 主要執行非多態的轉換操作,用於代替C中通常的轉換操作。
- 隱式轉換都建議使用 static_cast 進行標明和替換。
- 不能使用 static_cast 在有類型指針內轉換。
(3)示例程序如下所示:
#include <iostream>
using namespace std;
class CBase // 基類(父類)
{
};
class CDerived : public CBase // 派生類(子類)
{
};
int main()
{
// 1. 使用static_cast在基本數據類型之間轉換
float fval = 10.12;
int ival = static_cast<int>(fval); // float --> int
cout << ival << endl; // out: 10
// 2. 使用static_cast在有類型指針與void *之間轉換
int *intp = &ival;
void *voidp = static_cast<void *>(intp); // int* --> void*
// cout << *voidp << endl; // error,voidp的大小未知
long *longp = static_cast<long *>(voidp);
cout << *longp << endl; // out: 10
// 3. 用於類層次結構中基類和派生類之間指針或引用的轉換
// 上行轉換(派生類---->基類)是安全的
CDerived *tCDerived1 = nullptr;
CBase *tCBase1 = static_cast<CBase*>(tCDerived1);
// 下行轉換(基類---- > 派生類)由於沒有動態類型檢查,所以是不安全的
CBase *tCBase2 = nullptr;
CDerived *tCDerived2 = static_cast<CDerived*>(tCBase2); //不會報錯,但是不安全
// 不能使用static_cast在有類型指針內轉換
float *floatp = &fval; //10.12的addr
//int *intp1 = static_cast<int *>(floatp); // error,不能使用static_cast在有類型指針內轉換
cout << *floatp << endl; // out: 10.12
}
/*
輸出結果:
10
10
10.12
*/
二、dynamic_cast(動態轉換)
(1)使用場景
- 用於將一個父類的指針/引用轉化為子類的指針/引用(下行轉換)。
(2)使用特點
- 基類必須要有虛函數,因為 dynamic_cast 是運行時類型檢查,需要運行時類型信息,而這個信息是存儲在類的虛函數表中。
- 對於下行轉換,dynamic_cast 是安全的(當類型不一致時,轉換過來的是空指針),而 static_cast 是不安全的。
- 對指針進行 dynamic_cast,失敗返回 NULL,成功返回正常 cast 后的對象指針;對引用進行 dynamic_cast,失敗拋出一個異常,成功返回正常 cast 后的對象引用。
(3)示例程序如下所示:
#include <iostream>
using namespace std;
class CBase // 基類(父類)
{
public:
// dynamic_cast在將父類cast到子類時,父類必須要有虛函數
virtual int test() { return 0; } // 一定要是 virtual
};
class CDerived : public CBase // 派生類(子類)
{
public:
int test() { return 1; }
};
int main()
{
CBase *p_CBase = new CBase; // 基類對象指針
CDerived *p_CDerived = dynamic_cast<CDerived *>(p_CBase); // 將基類對象指針類型轉換為派生類對象指針
CBase i_CBase; // 創建基類對象
CBase &r_CBase = i_CBase; // 基類對象的引用
CDerived &r_CDerived = dynamic_cast<CDerived &>(r_CBase); // 將基類對象的引用轉換派生類對象的引用
}
三、const_cast(常量轉換)
(1)使用場景
- 常量指針(或引用)與非常量指針(或引用)之間的轉換。
(2)使用特點
- cosnt_cast 是四種類型轉換符中唯一可以對常量進行操作的轉換符。
- 去除常量性是一個危險的動作,盡量避免使用。
(3)示例程序如下所示:
#include <iostream>
using namespace std;
int main()
{
int value = 100;
const int *cpi = &value; // 定義一個常量指針
//*cpi = 200; // 不能通過常量指針修改值
// 1. 將常量指針轉換為非常量指針,然后可以修改常量指針指向變量的值
int *pi = const_cast<int *>(cpi);
*pi = 200;
// 2. 將非常量指針轉換為常量指針
const int *cpi2 = const_cast<const int *>(pi); // *cpi2 = 300; //已經是常量指針
const int value1 = 500;
const int &c_value1 = value1; // 定義一個常量引用
// 3. 將常量引用轉換為非常量引用
int &r_value1 = const_cast<int &>(c_value1);
// 4. 將非常量引用轉換為常量引用
const int &c_value2 = const_cast<const int &>(r_value1);
}
四、reinterpret_cast(不相關類型的轉換)
reinterpret 的英文含義有重新轉換的含義,就相當於 C 語言中不相關類型的轉換,強轉。
(1)使用場景
- 用在任意指針(或引用)類型之間的轉換。
- 能夠將整型轉換為指針,也可以把指針轉換為整型或數組。
(2)使用特點
- reinterpret_cast 是從底層對數據進行重新解釋,依賴具體的平台,可移植性差。
- 不到萬不得已,不用使用這個轉換符,高危操作。
(3)示例程序如下所示:
#include <iostream>
using namespace std;
int main()
{
int value = 100;
// 1. 用在任意指針(或引用)類型之間的轉換
double *pd = reinterpret_cast<double *>(&value);
cout << "*pd = " << *pd << endl;
// 2. reinterpret_cast能夠將指針值轉化為整形值
int *pv = &value;
int pvaddr = reinterpret_cast<int>(pv);
cout << "pvaddr = " << hex << pvaddr << endl;
cout << "pv = " << pv << endl;
}
/*
輸出結果:
*pd = -9.25596e+61
pvaddr = 8ffe60
pv = 008FFE60
*/
五、擴展
下面程序中,參數 pb 指向的是 B 類對象,pd1 的值不為0,而 pd2 的值為 0。
#include <iostream>
using namespace std;
class B
{
int m_iNum;
virtual void foo() {};
};
class D:public B
{
char *m_szName[100];
};
void func(B* pb)
{
D *pd1 = static_cast<D *>(pb);
D *pd2 = dynamic_cast<D *>(pb);
cout << pd1 << endl; //00CFF7C0
cout << pd2 << endl; //00000000
}
int main()
{
B pb; //父類對象pb
cout << "&pb: " << &pb << endl; //&pb: 00CFF7C0
func(&pb);
return 0;
}
/*
輸出結果:
&pb: 00CFF7C0
00CFF7C0
00000000
*/
