static_cast, dynamic_cast, const_cast学习和探讨
一?C語言中存在著兩種類型轉換:
隱式轉換和顯式轉換
隱式轉換:不同數據類型之間賦值和運算,函數調用傳遞參數……編譯器完成
1)?算術轉換(Arithmetic?conversion)?:?在混合類型的算術表達式中,?最寬的數據類型成為目標轉換類型。
2)?一種類型表達式賦值給另一種類型的對象:目標類型是被賦值對象的類型
3)?將一個表達式作為實參傳遞給函數調用,此時形參和實參類型不一致:目標轉換類型為形參的類型
4)從一個函數返回一個表達式,表達式類型與返回類型不一致:目標轉換類型為函數的返回類型
顯示轉換:在類型前增加?:(Type)變量?對變量進行的轉換。用戶顯式增加
二?C++中的類型轉換
通過這兩種方式,C語言中大部分的類型轉換都可以順利進行。至于能不能進行轉換,轉換后的結果如何,編譯器不管需要用戶自己去控制。C++繼承了C中的隱式和顯式轉換的方式。但這種轉換并不是安全和嚴格的,加上C++本身對象模型的復雜性,C++增加了四個顯示轉換的關鍵字。(C++是強類型語言)
(static_cast,dynamic_cast,const_static,reinterpret_cast)
?
//
static_cast:?靜態類型轉換;一般是普通數據類型(如int?m=static_cast<int>(3.14));只能提供編譯時的類型安全;一般的轉換,如果你不知道該用哪個,就用這個。
dynamic_cast:?動態類型轉換;一般用在父類和子類指針或應用的互相轉化;可以提供運行時類型安全
const_cast:?常量類型轉換;?把cosnt或volatile屬
reinterpret_cast:?重新解釋類型轉換;很像c的一般類型轉換操作性去掉
?
//
static_cast
(1)用于基本的數據類型轉換(char,int),及指針之間的轉換
(2)類層次中基類與子類成員函數指針的轉換
(3)類層次結構中基類與子類指針或引用之間的轉換
上行轉換:子類指針或引用轉換成基類表示——安全
下行轉換:基類指針或引用轉換成子類表示——危險(沒有動態類型檢查)
(4)把空指針轉換成目標類型的空指針。(不安全!!)
(5)把任何類型的表達式轉換成void類型。
?
注意:static_cast不能轉換掉expression_r_r的const、volitale、或者__unaligned屬性。
?
static_cast和普通強制轉換的區別:
//兩個無關類,
B*?pB?=new?B();
A?*pA1?=?(A?*)pB;//正常運行
A?*pA2?=?static_cast<A?*>(pB);//error?C2440:?“static_cast”:?無法從“B?*”轉換為“A?*”
?
static_cast和reinterpret_cast的區別:
C++primer第十五章里寫了編譯器隱式執行任何類型轉換都可由static_cast顯示完成;reinterpret_cast通常為操作數的位模式提供較低層的重新解釋
?
//
dynamic_cast:
用法:dynamic_cast?<?type-id?>?(?expression_r_r?)
該運算符把expression_r_r轉換成type-id類型的對象。Type-id必須是類的指針、類的引用或者void?*;如果type-id是類指針類型,那么expression_r_r也必須是一個指針,如果type-id是一個引用,那么expression_r_r也必須是一個引用。
dynamic_cast主要用于類層次間的上行轉換和下行轉換,還可以用于類之間的交叉轉換。在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的;在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。
?
(1)繼承關系的類指針對象或引用之間轉換
(2)包含有虛函數之間對象指針的轉換
?
除了基類指針指向子類對象,可以沒有虛函數外,其它要進行dynamic_cast轉換必須具有虛函數才行。下面解釋為什么:
虛函數對于dynamic_cast轉換的作用
Dynamic_cast轉換是在運行時進行轉換,運行時轉換就需要知道類對象的信息(繼承關系等)。如何在運行時獲取到這個信息——虛函數表。C++對象模型中,對象實例最前面的就是虛函數表指針,通過這個指針可以獲取到該類對象的所有虛函數,包括父類的。因為派生類會繼承基類的虛函數表,所以通過這個虛函數表,我們就可以知道該類對象的父類,在轉換的時候就可以用來判斷對象有無繼承關系。
所以虛函數對于正確的基類指針轉換為子類指針是非常重要的。
情況1:?static_cast轉換???????
class?A
{
};
class?B:public?A
{
public:
????int?m;?????????//B?成員
};
A*?pObjA?=?new?A();
B*?pObjB??=?NULL;
pObjB?=?static_cast<B*>(pObjA);???//基類指針轉化為子類指針?成功轉換
pObjB->m?=?10;?????????????????//實際中pObj所指向的對象?是A類對象
//上面會發生什么呢,在VC6.0中正常運行。。。?(測試結果正常運行)
//如果:
pObjB?=?dynamic_cast<B*>(pObjA);?//error?基類A沒有虛函數?不構成多態
?
情況2:?????dynamic_cast轉換????
class?A
{
public:
???virtual?~A(){}?????????//虛函數?多態
};
class?B:public?A
{
public:
????int?m;
};
A*?pObjA?=?new?A();
B*?pObjB??=?NULL;
pObjB?=?dynamic_cast<B*>(pObjA);??//編譯通過?
//實際運行結果:pObjB?==?NULL???//?dynamic_cast保證轉換無效?返回NULL
?
dynamic_cast轉換的安全性
dynamic_cast是動態轉換,只有在基類指針轉換為子類指針時才有意義。(子類指針轉換為基類指針本來就是可以的:基類指針指向子類對象OK)。
?
但是基類指針轉換為子類指針,并不是每一次都有效:只有基類指針本身指向的是一個派生類的對象,然后將此基類指針轉換為對應的派生類指針才是有效的。這種情況在表面上是無法判定的。此時dynamic就發揮了作用。dynamic_cast主要用于類層次間的上行轉換和下行轉換,還可以用于類之間的交叉轉換。在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的;在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。
?
示例1:
class?B
{
public:
int?m_iNum;
virtual?void?foo();
};
?
class?D:public?B
{
public:
char?*m_szName[100];
};
?
void?func(B?*pb)
{
D?*pd1?=?static_cast<D?*>(pb);
D?*pd2?=?dynamic_cast<D?*>(pb);
}
?
在上面的代碼段中:
如果pb指向一個D類型的對象,pd1和pd2是一樣的,并且對這兩個指針執行D類型的任何操作都是安全的;但是,如果pb指向的是一個?B類型的對象,那么pd1將是一個指向該對象的指針,對它進行D類型的操作將是不安全的(如訪問m_szName),而pd2將是一個空指針。另外要注意:B要有虛函數,否則會編譯出錯;static_cast則沒有這個限制。這是由于運行時類型檢查需要運行時類型信息,而這個信息存儲在類的虛函數表(關于虛函數表的概念,詳細可見<Inside?c++?object?model>)中,只有定義了虛函數的類才有虛函數表,沒有定義虛函數的類是沒有虛函數表的。
?
示例2:
另外,dynamic_cast還支持交叉轉換(cross?cast)。如下代碼所示。
class?A
{
public:
int?m_iNum;
virtual?void?f(){}
};
class?B:public?A{};
class?D:public?A{};
?
int?_tmain(int?argc,?_TCHAR*?argv[])
{
B?*pb?=?new?B;
pb->m_iNum?=?100;
D?*pd1?=?static_cast<D?*>(pb);?//編譯出錯?error?C2440:?“static_cast”:?無法從“B?*”轉換為“D?*”
D?*pd2?=?dynamic_cast<D?*>(pb);?//pd2?is?NULL
delete?pb;
?
return?0;
}
使用static_cast進行轉換是不被允許的,將在編譯時出錯;而使用dynamic_cast的轉換則是允許的,結果是空指針。
const_cast
用法:const_cast<type_id>?(expression_r_r)
該運算符用來修改類型的const或volatile屬性。除了const?或volatile修飾之外,?type_id和expression_r_r的類型是一樣的。
常量指針被轉化成非常量指針,并且仍然指向原來的對象;常量引用被轉換成非常量引用,并且仍然指向原來的對象;常量對象被轉換成非常量對象。
Voiatile和const類試。舉如下一例:
class?B
{
public:
int?m_iNum;
}
int?_tmain(int?argc,?_TCHAR*?argv[])
{
const?B?b1;
b1.m_iNum?=?100;?//編譯出錯:error?C3892:?“b1”:?不能給常量賦值
B?&b2?=?static_cast<B&>(b1);//編譯出錯:error?C2440:?“static_cast”:?無法從“const?B”轉換為“B?&”
B?b2?=?const_cast<B>(b1);//編譯出錯:error?C2440:?“const_cast”:?無法從“const?B”轉換為“B”
B?&b2?=?const_cast<B&>(b1);
b2.?m_iNum?=?200;?//fine
?
return?0;
}*/
?
來源:為什么需要dynamic_cast強制轉換?
簡單的說,當無法使用virtual函數的時候
典型案例:
Wicrosoft公司提供給我們一個類庫,其中提供一個類Employee.以頭文件Eemployee.h和類庫.lib分發給用戶
顯然我們并無法得到類的實現的源代碼
//Emplyee.h
class?Employee?
{
public:
????virtual?int?salary();
};
class?Manager?:?public?Employee
{
public:?
????int?salary();
};
class?Programmer?:?public?Employee
{
public:
????int?salary();
};
我們公司在開發的時候建立有如下類:
class?MyCompany
{
public:
????void?payroll(Employee?*pe);
????//
};
void?MyCompany::payroll(Employee?*pe)
{
????//do?something
}
但是開發到后期,我們希望能增加一個bonus()的成員函數到W$公司提供的類層次中。
假設我們知道源代碼的情況下,很簡單,增加虛函數:
//Emplyee.h
class?Employee?
{
public:
????virtual?int?salary();
????virtual?int?bonus();
};
class?Manager?:?public?Employee
{
public:?
????int?salary();
};
class?Programmer?:?public?Employee
{
public:
????int?salary();
????int?bonus();
};
//Emplyee.cpp
int?Programmer::bonus()
{
????//
}
payroll()通過多態來調用bonus()
class?MyCompany
{
public:
????void?payroll(Employee?*pe);
????//
};
void?MyCompany::payroll(Employee?*pe)
{
????//do?something
????//pe->bonus();
}
但是現在情況是,我們并不能修改源代碼,怎么辦?dynamic_cast華麗登場了!
在Employee.h中增加bonus()聲明,在另一個地方定義此函數,修改調用函數payroll().重新編譯,ok
//Emplyee.h
class?Employee?
{
public:
????virtual?int?salary();
};
class?Manager?:?public?Employee
{
public:?
????int?salary();
};
class?Programmer?:?public?Employee
{
public:
????int?salary();
????int?bonus();//直接在這里擴展
};
//somewhere.cpp
int?Programmer::bonus()
{
????//define
}
?
class?MyCompany
{
public:
????void?payroll(Employee?*pe);
????//
};
void?MyCompany::payroll(Employee?*pe)
{
????Programmer?*pm?=?dynamic_cast<Programmer?*>(pe);
????
????//如果pe實際指向一個Programmer對象,dynamic_cast成功,并且開始指向Programmer對象起始處
????if(pm)
????{
????????//call?Programmer::bonus()
????}
????//如果pe不是實際指向Programmer對象,dynamic_cast失敗,并且pm?=?0
????else
????{
????????//use?Employee?member?functions
????}
}
?
?
?
reinterpret_cast
可以轉換任意一個32bit整數,包括所有的指針和整數。可以把任何整數轉成指針,也可以把任何指針轉成整數,以及把指針轉化為任意類型的指針,
威力最為強大!但不能將非32bit的實例轉成指針。總之,只要是32bit的東東,怎么轉都行!??
*/
?
前言
這篇文章總結的是C++中的類型轉換,這些小的知識點,有的時候,自己不是很注意,但是在實際開發中確實經常使用的。俗話說的好,不懂自己寫的代碼的程序員,不是好的程序員;如果一個程序員對于自己寫的代碼都不懂,只是知道一昧的的去使用,終有一天,你會迷失你自己的。
C++中的類型轉換分為兩種:
而對于隱式變換,就是標準的轉換,在很多時候,不經意間就發生了,比如int類型和float類型相加時,int類型就會被隱式的轉換位float類型,然后再進行相加運算。而關于隱式轉換不是今天總結的重點,重點是顯式轉換。在標準C++中有四個類型轉換符:static_cast、dynamic_cast、const_cast和reinterpret_cast;下面將對它們一一的進行總結。
static_cast
static_cast的轉換格式:static_cast <type-id> (expression)
將expression轉換為type-id類型,主要用于非多態類型之間的轉換,不提供運行時的檢查來確保轉換的安全性。主要在以下幾種場合中使用:
當進行上行轉換,也就是把子類的指針或引用轉換成父類表示,這種轉換是安全的;
當進行下行轉換,也就是把父類的指針或引用轉換成子類表示,這種轉換是不安全的,也需要程序員來保證;
注:static_cast不能轉換掉expression的const、volatile和__unaligned屬性。
dynamic_cast
dynamic_cast的轉換格式:dynamic_cast <type-id> (expression)
將expression轉換為type-id類型,type-id必須是類的指針、類的引用或者是void *;如果type-id是指針類型,那么expression也必須是一個指針;如果type-id是一個引用,那么expression也必須是一個引用。
dynamic_cast主要用于類層次間的上行轉換和下行轉換,還可以用于類之間的交叉轉換。在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的;在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。在多態類型之間的轉換主要使用dynamic_cast,因為類型提供了運行時信息。下面我將分別在以下的幾種場合下進行dynamic_cast的使用總結:
比如B繼承自A,B轉換為A,進行上行轉換時,是安全的,如下: #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 }
C繼承自B,B繼承自A,這種多重繼承的關系;但是,關系很明確,使用dynamic_cast進行轉換時,也是很簡單的: class A {// ...... }; class B : public A {// ...... }; class C : public B {// ...... }; int main() {C *pC = new C;B *pB = dynamic_cast<B *>(pC); // OKA *pA = dynamic_cast<A *>(pC); // OK }
而上述的轉換,static_cast和dynamic_cast具有同樣的效果。而這種上行轉換,也被稱為隱式轉換;比如我們在定義變量時經常這么寫:B *pB = new C;這和上面是一個道理的,只是多加了一個dynamic_cast轉換符而已。
可以將類轉換成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 ApV = dynamic_cast<void *>(pB); // pV points to an object of B }
但是,在類A和類B中必須包含虛函數,為什么呢?因為類中存在虛函數,就說明它有想讓基類指針或引用指向派生類對象的情況,此時轉換才有意義;由于運行時類型檢查需要運行時類型信息,而這個信息存儲在類的虛函數表中,只有定義了虛函數的類才有虛函數表。
這個就是下行轉換,從基類指針轉換到派生類指針。
對于一些復雜的繼承關系來說,使用dynamic_cast進行轉換是存在一些陷阱的;比如,有如下的一個結構:
D類型可以安全的轉換成B和C類型,但是D類型要是直接轉換成A類型呢?
如果進行上面的直接轉,你將會得到一個NULL的pA指針;這是因為,B和C都繼承了A,并且都實現了虛函數Func,導致在進行轉換時,無法進行抉擇應該向哪個A進行轉換。正確的做法是:
int main() {D *pD = new D;B *pB = dynamic_cast<B *>(pD);A *pA = dynamic_cast<A *>(pB); }這就是我在實現QueryInterface時,得到IUnknown的指針時,使用的是*ppv = static_cast<IX *>(this);而不是*ppv = static_cast<IUnknown *>(this);
對于多重繼承的情況,從派生類往父類的父類進行轉時,需要特別注意;比如有下面這種情況:
現在,你擁有一個A類型的指針,它指向E實例,如何獲得B類型的指針,指向E實例呢?如果直接進行轉的話,就會出現編譯器出現分歧,不知道是走E->C->B,還是走E->D->B。對于這種情況,我們就必須先將A類型的指針進行下行轉換,獲得E類型的指針,然后,在指定一條正確的路線進行上行轉換。
上面就是對于dynamic_cast轉換的一些細節知識點,特別是對于多重繼承的情況,在實際項目中,很容易出現問題。
const_cast
const_cast的轉換格式:const_cast <type-id> (expression)
const_cast用來將類型的const、volatile和__unaligned屬性移除。常量指針被轉換成非常量指針,并且仍然指向原來的對象;常量引用被轉換成非常量引用,并且仍然引用原來的對象。看以下的代碼例子:
/* ** FileName : ConstCastDemo ** Author : Jelly Young ** Date : 2013/12/27 ** Description : More information, please go to http://www.jellythink.com */ #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; // ErrorCA *pB = const_cast<CA *>(pA);pB->m_iA = 100;// Now the pA and the pB points to the same objectcout<<pA->m_iA<<endl;cout<<pB->m_iA<<endl;const CA &a = *pA;// a.m_iA = 200; // ErrorCA &b = const_cast<CA &>(a);pB->m_iA = 200;// Now the a and the b reference to the same objectcout<<b.m_iA<<endl;cout<<a.m_iA<<endl; }注:你不能直接對非指針和非引用的變量使用const_cast操作符去直接移除它的const、volatile和__unaligned屬性。
reinterpret_cast
reinterpret_cast的轉換格式:reinterpret_cast <type-id> (expression)
允許將任何指針類型轉換為其它的指針類型;聽起來很強大,但是也很不靠譜。它主要用于將一種數據類型從一種類型轉換為另一種類型。它可以將一個指針轉換成一個整數,也可以將一個整數轉換成一個指針,在實際開發中,先把一個指針轉換成一個整數,在把該整數轉換成原類型的指針,還可以得到原來的指針值;特別是開辟了系統全局的內存空間,需要在多個應用程序之間使用時,需要彼此共享,傳遞這個內存空間的指針時,就可以將指針轉換成整數值,得到以后,再將整數值轉換成指針,進行對應的操作。
總結
這篇博文總結了C++中的類型轉換,重點總結了其中的顯式轉換。對于C++支持的這四種顯式轉換都進行了詳細的描述。如果大家有什么補充的,或者我總結的有誤的地方,請大家多多指教。
聽聞雷哥在北京面試不順,公司對數據結構和算法都要求很多,過段日子準備再將數據結構和算法進行整理一下。
2013年12月27日 于大連,東軟。
總結
以上是生活随笔為你收集整理的static_cast, dynamic_cast, const_cast学习和探讨的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: _cdecl和_stdcal的区别
- 下一篇: ADO学习(三)Command 对象