1. 程式人生 > >dynamic_cast、static_cast、const_cast和reinterpret_cast的區別

dynamic_cast、static_cast、const_cast和reinterpret_cast的區別

           C++的型別轉換分為兩種:隱式轉換和顯示轉換。
           一、 對於隱式轉換,就是標準的轉換,在很多時候,不經意間就發生了,例如int和float加法,int型別就會被隱式轉換為float了,這種稱為升級轉換。還有就是把等號右邊的值轉換為左邊的型別,再賦值。還有類變數作為引數傳遞給函式:
class B
{
	public:
		int num;
	B(int a)
	{
		num=a;
	}
}
B show(B b)
{
	return b;
}
show(5);//這裡也發生了隱式轉換,將5轉換為B型別的(B)5;
B b=10;//也是OK的,發生了隱式轉換,除非加上explicit;
二、顯示轉換:在C++中有四個型別轉換符:static_cast 、dynamic_cast  、const_cast和 reinterpret_cast。

下面對他們--進行小小的總結:

1、static_cast(編譯器在編譯期處理)

        轉換格式:static_cast<type-id>(expression)。

      將expression轉換為type-id型別,type-id和expression必須是指標、引用、算術型別或列舉型別。一般的轉換(no run-time check),不提供執行時的檢查來確保轉換的安全性。
主要在一下幾個場合中使用:
(1).用於類層次結構中,基類和子類之間指標和引用的轉換;
          當進行上行轉換時:也就是把子類的指標或者引用轉換為父類表示,這種轉換時安全的;
          當進行下行轉換時:也就是把父類的指標和引用轉換成子類表示,這種轉換時不安全的,需要程式設計師來保證;
(2)、用於基本資料之間的型別轉換,例如int和float,char和int之間等,這種也是不安全的,也需要程式設計師的保證
(3)、把void轉換為目標型別的指標,是及其不安全的;
注:static_cast不能轉換掉expression為const、volatile、以及unaligned屬性。
2、dynamic_cast(執行期間確定型別)

                  C程式設計師大概都喜歡用強制型別轉換,它雖然優缺點,但是考慮到它的靈活我們還是用的不亦樂乎。大多數情況下我們都會轉換成功,只有少量的情況下才會轉換失敗,失敗了怎麼辦,這時候就用到了dynamic_cast了。這裡的“少量情況”是這樣的,如果有多繼承的類物件,你在某些情況下要得到物件的指標,而你又將其轉換為某種型別,但是由於C++物件型別的多型性(可以有多種型別),你又不確定一定會成功(執行的時候才確定其型別),此時可以利用dynamic_cast,充分利用C++的執行時檢查機制。
例如:

class A{...}; 
class B:public A{...}; 
class C:public B{...}; 
void Fun1(B* pB) 
{ 
A* pA  = (A*)pB; 
C* pC  = (C*)pB; 
} 
       Fun1函式使用強制型別轉換將pB轉換為A*或C*,看出什麼的問題了嗎?
      如果這樣呼叫Fun1:      Fun1(((B*)new C));的確不會有問題,

       但如果是這樣呢:   Fun1(new B); 型別的轉換成功,pC不會為NULL,因為強轉總是會成功的,但是達不到我們的目的,因為當使用到pC指標時就程式就悲劇了,崩潰(具體解決辦法後面有詳細的分析).
       如果情況更糟:   Fun1((B*)0X00005678);   //0X00005678是一個隨機值
pA,pC都不會是NULL,再說一遍強制型別轉換總是能夠成功的,重要的事情說兩遍。但使用這兩個指標時程式肯定崩潰.當然你可以使用異常處理機制來處理這樣的錯誤,不過這有點大才小用的感覺,最好能夠找到一種能夠檢查出型別轉換能否成功的辦法,這時dynamic_cast就能大顯身手了.詳解正解請往下看。

         dynamic_cast的轉換格式:dynamic_cast<type-id>(expression) 

          將expression轉換為type-id型別,type-id必須是類的指標、類的引用或者是void*;並且expression和type-id的型別必須一致;

        在執行期,會檢查這個轉換是否可能。僅能應用於指標或者引用,不支援內建資料型別,因為只有指標和引用才能實現多型,賦值和傳值就會出現物件的分割,不會實現多型。通常在基類和派生類之間轉換時使用 。基本用法如下:

        T1 obj;
        T2* pObj = dynamic_cast<T2*>(&obj);    //轉換為T2指標失敗返回NULL
        T2& refObj = dynamic_cast<T2&>(obj);  //轉換為T2引用失敗丟擲bad_cast異常
       注:在使用時需要注意:被轉換物件obj的型別T1必須是多型型別,即T1必須公有繼承自其它類,或者T1擁有虛擬函式(繼承或自定義)。若T1為非多型型別,使用dynamic_cast會報編譯錯誤。 
       很明顯,為了讓dynamic_cast能正常工作,必須讓編譯器支援執行期型別資訊(RTTI)。
     (1)、(up cast)向上轉型和static_cast轉換一樣,是安全的,是多型的基礎,不需要任何輔助方法,總是成功的;
#include <iostream>
using namespace std;
class A{};
classB:public A{};
int main()
{
	B *b=new B;
	A *a=dynamic_cast<A*>(b);  //Safe and success!
	B *b=dynamic_cast<B*>(b);  //Safe and success!
	void *vp=dynamic_cast<void *>(c);//success vp points to an object C;
	system("pause");
	return 0;
}
    static_cast和dynamic_cast具有相同的效果,而這種上行轉換也為隱式轉換;和我們的定義:A *a=new C;一樣;只是多加了一個dynamic_cast轉換符而已。
      注意下面一種特殊的上行轉換:在多繼承的時候容易造成訪問不明確(用虛繼承解決),這時候型別的轉換也會出現問題!例如:B繼承A,C繼承A,D繼承AB,
#include <iostream>


using namespace std;
class A{};
class B:public A{};
class C:public A{};
class D:public B,public C{};
int main()
{
	D *d=new D;
	A *a=dynamic_cast<A *>(d);//這是因為BC都繼承了A,那麼從D轉換到A就有兩條路可以走,DBA或者DCA,這樣的話,計算機就不知道怎麼走了,那麼a=NULL;
	system("pause");
	return 0;
}
(2)(down cast)向下轉型:下行轉換時dynamic_cast具有型別檢查的功能,比static_cast安全;在多型類之間的轉換主要用dynamic_cast,因為型別提供了
執行時的資訊。如果expression是type-id的基類,使用dynamic_cast進行轉換時,在執行時就會檢查expression是否真正的指向一個type-id型別的指標,如果是則進行正確的轉化,獲得相應的值;如果不對,則返回NULL,如果是引用則在執行時會丟擲異常;
#include <iostream>
using namespace std;
class A{};
class B:public A{};
int main()
{
	A *a=new B;
	A *aa=new A;
	B *a1=dynamic_cast<B*>(a);  // a1不為NULL,a本來指向B,Safe and success!
	B *a2=dynamic_cast<B*>(aa)  // error,aa指向A,並不指向B ,返回NULL,即a2=NULL; 
	system("pause");
	return 0;
}

看到這裡,回到上面的問題:具體做法就是:

A* pA  = dynamic_cast<A*>pB;// upcast. 
if (NULL == pA){...}  //OK,不為NULL;
C* pC  = dynamic_cast<C*>pB;// downcast. 
if (NULL == pC){...}   //error,顯然為空NULL;
       dynamic_cast < ObjectType-ID* > ( ObjectType*)
      如果要成功地將ObjectType*轉換為ObjectType-ID*,則必須存在這種可能性才可以,也就是說ObjectType*指向的物件要"包含"ObjectType-ID*指向的物件,如此才能夠成功.就上面的例子來說,C物件"包含"B物件,而B物件"包含"A物件,如果:
                      A* pA = new B;
那麼
                   B* pB  = dynamic_cast<B*>pA;// OK.
                   C* pC  = dynamic_cast<C*>pA;// Fail.
如果說你不能確定這種包含關係,最好使用dynamic_cast.
 實際上可以把dynamic_cast看做是更嚴格檢查的強制型別轉換,因為"更嚴格"所以能夠檢查出來錯誤.
(3)(cross cast)橫向轉型:

橫向轉型的時候一定要注意是否有繼承關係,如果沒有繼承關係的兩個類是不能轉換的。

例子如下:

class Shape {};
class Rollable {};
class Circle : public Shape, public Rollable {};
class Square : public Shape {};

//cross cast fail
Shape *pShape1 = new Square();
Rollable *pRollable1 = dynamic_cast<Rollable*>(pShape1);//pRollable為NULL

//cross cast success
Shape *pShape2 = new Circle();
Rollable *pRollable2 = dynamic_cast<Rollable*>(pShape2);//pRollable不為NULL

3、const_cast (編譯器在編譯期處理)

const_cast的轉換格式:const_cast <type-id> (expression)
const_cast用來將型別的const、volatile和__unaligned屬性移除,主要針對const和volatile的轉換。常量指標被轉換成非常量指標,並且仍然指向原來的物件;常量引用被轉換成非常量引用,並且仍然引用原來的物件。

注意常量物件只能呼叫常量函式,常量函式只能輸出,不能改變成員屬性的值,所以這裡需要const_cast來轉換我非常量的指標物件來進行下一步的操作。

#include <iostream>
using namespace std;
class A
{
public:
	A():num(10)
	{
		cout<<"A"<<endl;
	}
	int num;
};
int main()
{
	const A *a=new A;
	A *a3=new A;
	cout<<a->num<<endl;
	//a->num=100; //error;因為常量物件不允許改變類的成員屬性;
	A *aa=const_cast<A *>(a);
	aa->num=100;//OK,經過const_cast的轉化,aa不是常量物件;
	cout<<a->num<<endl;

	const A &a1=*a;
	//a1->num=200; //error;常量物件的引用也不能改變成員屬性;
	A &a2=const_cast<A&>(*a);
	a2.num=200;
	cout<<a2.num<<endl;//OK,經過const_cast的轉化,a2不是常量物件的引用;
	//現在a2和aa都是指向同一個物件。
	
	//A a4=const_cast<A>(*a3);  //error,,不能直接使用const_cast對非const,volatile和nuligned去掉該屬性。
	const char* p = "123";   
	char* c = const_cast<char*>(p);   <span>
</span><pre name="code" class="cpp">        c[0] = 1;   //error,表面上通過編譯去掉了const性,但是操作其地址時系統依然不允許這麼做。
char *pp="keepgoing";const char *p1=const_cast<const char *>(pp);system("pause");return 0;}

執行結果:

      

  對於本身定義時為const的型別,即使你去掉const性,在你操作這片內容時候也要小心,只能r不能w操作,否則還是會出錯。

      const_cast操作不能在不同的種類間轉換。相反,它僅僅把一個它作用的表示式轉換成常量。它可以使一個本來不是const型別的資料轉換成const型別的,或者把const屬性去掉。儘量不要使用const_cast,如果發現呼叫自己的函式,竟然使用了const_cast,那就趕緊打住,重新考慮一下設計吧。
(4)reinterpret_cast (編譯器在編譯期處理)

reinterpret_cast的轉換格式:reinterpret_cast <type-id> (expression)

         用於進行沒有任何關聯之間的轉換,比如一個字元指標轉換為一個整形數。允許將任何指標型別轉換為其它的指標型別;聽起來很強大,但是也很不靠譜。它主要用於將一種資料型別從一種型別轉換為另一種型別。它可以將一個指標轉換成一個整數,也可以將一個整數轉換成一個指標,在實際開發中,先把一個指標轉換成一個整數,在把該整數轉換成原型別的指標,還可以得到原來的指標值;特別是開闢了系統全域性的記憶體空間,需要在多個應用程式之間使用時,需要彼此共享,傳遞這個記憶體空間的指標時,就可以將指標轉換成整數值,得到以後,再將整數值轉換成指標,進行對應的操作。

博文資料參考:

http://riddickbryant.iteye.com/blog/547361

http://www.jb51.net/article/55968.htm

http://www.jb51.net/article/55885.htm

http://bbs.pediy.com/showthread.php?t=196996

http://www.cnblogs.com/weidagang2046/archive/2010/04/10/1709226.html

http://jetyi.blog.51cto.com/1460128/671256


感謝博主的分享!!