C++中的四種強制類型轉換符詳解


C++ 既支持 C 風格的類型轉換,又有自己風格的類型轉換。C 風格的轉換格式很簡單,但是有不少缺點:

  • 轉換太過隨意,可以在任意類型之間轉換。你可以把一個指向 const 對象的指針轉換成指向非 const 對象的指針,把一個指向基類對象的指針轉換成一個派生類對象的指針,這些轉換之間的差距是非常巨大的,但是傳統的C語言風格的類型轉換沒有區分這些。
  • C 風格的轉換沒有統一的關鍵字和標示符。對於大型系統,做代碼排查時容易遺漏和忽略。

C++ 風格完美的解決了上面兩個問題。

  1. 對類型轉換做了細分,提供了四種不同類型轉換,以支持不同需求的轉換;
  2. 類型轉換有了統一的標示符,利於代碼排查和檢視。

下面分別來介紹這四種轉換: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
*/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM