C++類型轉(zhuǎn)換總結(jié)
本章內(nèi)容:
1 前言
2 static_cast
3 dynamic_cast
4 const_cast
5 reinterpret_cast
1 前言
-
C++中類型轉(zhuǎn)換分為兩種:
- 隱式類型轉(zhuǎn)換
- 顯式類型轉(zhuǎn)換。
對于隱式類型轉(zhuǎn)換,其實(shí)就是標(biāo)準(zhǔn)的轉(zhuǎn)換,在很多時(shí)候不經(jīng)意間就發(fā)生了,比如
int
和float
相加的時(shí)候,int
類型就會被隱式轉(zhuǎn)換為float
類型,然后再進(jìn)行加運(yùn)算。而今天的重點(diǎn)主要講解顯式類轉(zhuǎn)換,在C++中有四個(gè)類型轉(zhuǎn)換符:static_cast
、dynamic_cast
、const_cast
和reinterpret_cast
,下面我們對這四個(gè)轉(zhuǎn)換符進(jìn)行總結(jié)。
2 static_cast
-
static_cast
的轉(zhuǎn)換格式:static_cast<type-id> (expression)
- 將
expression
轉(zhuǎn)換為type-id
類型,主要用于非多態(tài)類型之間的轉(zhuǎn)換,不提供運(yùn)行時(shí)的檢查來確保轉(zhuǎn)換的安全性。主要在以下幾種場合中使用:- 用于類層次結(jié)構(gòu)中,基類和子類之間指針和引用的轉(zhuǎn)換;
當(dāng)進(jìn)行上行轉(zhuǎn)換,也就是把子類的指針或引用轉(zhuǎn)換成父類表示,這種轉(zhuǎn)換是安全的;
當(dāng)進(jìn)行下行轉(zhuǎn)換,也就是把父類的指針或引用轉(zhuǎn)換成子類表示,這種轉(zhuǎn)換是不安全的,也需要程序員來保證; - 用于基本數(shù)據(jù)類型之間的轉(zhuǎn)換,如把
int
轉(zhuǎn)換成char
,把int
轉(zhuǎn)換成enum
等等,這種轉(zhuǎn)換的安全性需要程序員來保證; - 把
void
指針轉(zhuǎn)換成目標(biāo)類型的指針,是及其不安全的;
注:static_cast
不能轉(zhuǎn)換expression
的const
、volatile
和__unaligned
屬性。
- 用于類層次結(jié)構(gòu)中,基類和子類之間指針和引用的轉(zhuǎn)換;
3 dynamic_cast
dynamic_cast
的轉(zhuǎn)換格式:dynamic_cast <type-id> (expression)
將
expression
轉(zhuǎn)換為type-id
類型,type-id
必須是類的指針、類的引用或者是void *
;如果type-id
是指針類型,那么expression
也必須是一個(gè)指針;如果type-id
是一個(gè)引用,那么expression
也必須是一個(gè)引用。-
dynamic_cast
主要用于類層次間的上行轉(zhuǎn)換和下行轉(zhuǎn)換,還可以用于類之間的交叉轉(zhuǎn)換。在類層次間進(jìn)行上行轉(zhuǎn)換時(shí),dynamic_cast
和static_cast
的效果是一樣的;在進(jìn)行下行轉(zhuǎn)換時(shí),dynamic_cast
具有類型檢查的功能,比static_cast
更安全。在多態(tài)類型之間的轉(zhuǎn)換主要使用dynamic_cast
,因?yàn)轭愋吞峁┝诉\(yùn)行時(shí)信息。下面我將分別在以下的幾種場合下進(jìn)行dynamic_cast
的使用總結(jié):- 最簡單的上行轉(zhuǎn)換
-
比如B繼承自A,B轉(zhuǎn)換為A,進(jìn)行上行轉(zhuǎn)換時(shí),是安全的,示例如下:
#include <iostream>
using namespace std;
class A
{
// ......
};
class B : public A
{
// ......
};
int main()
{
B *pB = new B;
A *pA = dynamic_cast<A *>(pB); // Safe and will succeed
}- 多重繼承之間的上行轉(zhuǎn)換
C繼承自B,B繼承自A,這種多重繼承的關(guān)系;但是,關(guān)系很明確,使用dynamic_cast
進(jìn)行轉(zhuǎn)換時(shí),也是很簡單的:
class A
{
// ......
};
class B : public A
{
// ......
};
class C : public B
{
// ......
};
int main()
{
C *pC = new C;
B *pB = dynamic_cast<B *>(pC); // OK
A *pA = dynamic_cast<A *>(pC); // OK
}
- 多重繼承之間的上行轉(zhuǎn)換
-
而上述的轉(zhuǎn)換,
static_cast
和dynamic_cast
具有同樣的效果。而這種上行轉(zhuǎn)換,也被稱為隱式轉(zhuǎn)換;比如我們在定義變量時(shí)經(jīng)常這么寫:B *pB = new C
;這和上面是一個(gè)道理的,只是多加了一個(gè)dynamic_cast
轉(zhuǎn)換符而已。- 轉(zhuǎn)換成void *
可以將類轉(zhuǎn)換成void *,例如:
class A
{
public:
virtual void f(){}
// ......
};
class B
{
public:
virtual void f(){}
// ......
};
int main()
{
A *pA = new A;
B *pB = new B;
void *pV = dynamic_cast<void *>(pA); // pV points to an object of A
pV = dynamic_cast<void *>(pB); // pV points to an object of B
}-
但是,在類A和類B中必須包含虛函數(shù),為什么呢?因?yàn)轭愔写嬖谔摵瘮?shù),就說明它有想讓基類指針或引用指向派生類對象的情況,此時(shí)轉(zhuǎn)換才有意義;由于運(yùn)行時(shí)類型檢查需要運(yùn)行時(shí)類型信息,而這個(gè)信息存儲在類的虛函數(shù)表中,只有定義了虛函數(shù)的類才有虛函數(shù)表。
- 如果
expression
是type-id
的基類,使用dynamic_cast
進(jìn)行轉(zhuǎn)換時(shí),在運(yùn)行時(shí)就會檢查expression
是否真正的指向一個(gè)type-id
類型的對象,如果是,則能進(jìn)行正確的轉(zhuǎn)換,獲得對應(yīng)的值;否則返回NULL,如果是引用,則在運(yùn)行時(shí)就會拋出異常;例如:
class B
{
virtual void f(){};
};
class D : public B
{
virtual void f(){};
};
void main()
{
B* pb = new D; // unclear but ok
B* pb2 = new B;
D* pd = dynamic_cast<D>(pb); // ok: pb actually points to a D
D pd2 = dynamic_cast<D*>(pb2); // pb2 points to a B not a D, now pd2 is NULL
}
- 如果
-
這個(gè)就是下行轉(zhuǎn)換,從基類指針轉(zhuǎn)換到派生類指針。
對于一些復(fù)雜的繼承關(guān)系來說,使用dynamic_cast進(jìn)行轉(zhuǎn)換是存在一些陷阱的;比如,有如下的一個(gè)結(jié)構(gòu):
轉(zhuǎn)換結(jié)構(gòu) -
D類型可以安全的轉(zhuǎn)換成B和C類型,但是D類型要是直接轉(zhuǎn)換成A類型呢?
class A { virtual void Func() = 0; }; class B : public A { void Func(){}; }; class C : public A { void Func(){}; }; class D : public B, public C { void Func(){} }; int main() { D *pD = new D; A *pA = dynamic_cast<A *>(pD); // You will get a pA which is NULL }
-
如果進(jìn)行上面的直接轉(zhuǎn),你將會得到一個(gè)
NULL
的pA
指針;這是因?yàn)椋珺和C都繼承了A,并且都實(shí)現(xiàn)了虛函數(shù)Func
,導(dǎo)致在進(jìn)行轉(zhuǎn)換時(shí),無法進(jìn)行抉擇應(yīng)該向哪個(gè)A進(jìn)行轉(zhuǎn)換。正確的做法是:int main() { D *pD = new D; B *pB = dynamic_cast<B *>(pD); A *pA = dynamic_cast<A *>(pB); }
這就是我在實(shí)現(xiàn)
QueryInterface
時(shí),得到IUnknown
的指針時(shí),使用的是*ppv = static_cast<IX *>(this);
而不是*ppv = static_cast<IUnknown *>(this);
-
對于多重繼承的情況,從派生類往父類的父類進(jìn)行轉(zhuǎn)時(shí),需要特別注意;比如有下面這種情況:
多重集成結(jié)構(gòu) 現(xiàn)在,你擁有一個(gè)A類型的指針,它指向E實(shí)例,如何獲得B類型的指針,指向E實(shí)例呢?如果直接進(jìn)行轉(zhuǎn)的話,就會出現(xiàn)編譯器出現(xiàn)分歧,不知道是走E->C->B,還是走E->D->B。對于這種情況,我們就必須先將A類型的指針進(jìn)行下行轉(zhuǎn)換,獲得E類型的指針,然后,在指定一條正確的路線進(jìn)行上行轉(zhuǎn)換。
4 const_cast
const_cast
的轉(zhuǎn)換格式:const_cast <type-id> (expression)
-
const_cast
用來將類型的const
、volatile
和__unaligned
屬性移除。常量指針被轉(zhuǎn)換成非常量指針,并且仍然指向原來的對象;常量引用被轉(zhuǎn)換成非常量引用,并且仍然引用原來的對象。看以下的代碼例子:#include <iostream> using namespace std; class CA { public: CA():m_iA(10){} int m_iA; }; int main() { const CA *pA = new CA; // pA->m_iA = 100; // Error CA *pB = const_cast<CA *>(pA); pB->m_iA = 100; // Now the pA and the pB points to the same object cout<<pA->m_iA<<endl; cout<<pB->m_iA<<endl; const CA &a = *pA; // a.m_iA = 200; // Error CA &b = const_cast<CA &>(a); b.m_iA = 200; // Now the a and the b reference to the same object cout<<b.m_iA<<endl; cout<<a.m_iA<<endl; }
注:你不能直接對非指針和非引用的變量使用
const_cast
操作符去直接移除它的const
、volatile
和__unaligned
屬性。
5 reinterpret_cast
-
reinterpret_cast
的轉(zhuǎn)換格式:reinterpret_cast <type-id> (expression)
- 允許將任何指針類型轉(zhuǎn)換為其它的指針類型;聽起來很強(qiáng)大,但是也很不靠譜。它主要用于將一種數(shù)據(jù)類型從一種類型轉(zhuǎn)換為另一種類型。它可以將一個(gè)指針轉(zhuǎn)換成一個(gè)整數(shù),也可以將一個(gè)整數(shù)轉(zhuǎn)換成一個(gè)指針,在實(shí)際開發(fā)中,先把一個(gè)指針轉(zhuǎn)換成一個(gè)整數(shù),在把該整數(shù)轉(zhuǎn)換成原類型的指針,還可以得到原來的指針值;特別是開辟了系統(tǒng)全局的內(nèi)存空間,需要在多個(gè)應(yīng)用程序之間使用時(shí),需要彼此共享,傳遞這個(gè)內(nèi)存空間的指針時(shí),就可以將指針轉(zhuǎn)換成整數(shù)值,得到以后,再將整數(shù)值轉(zhuǎn)換成指針,進(jìn)行對應(yīng)的操作。