1.C和C++類型轉換
C風格的強制轉換:
T(exdivssion)或者(T)exdivssion
C++的類型轉換用:dynamic_cast,static_cast,reinterdivt_cast,const_cast
2.運行時類型識別(RTTI)
(1)定義:程序能夠使用基類的指針或引用來檢查這些指針或引用所指的對象的實際派生類型。
(2)RTTI提供了兩個有用的操作符:
typeid:返回指針或引用所指的實際類型
dynamic_cast:用於將一個指向派生類的基類指針或引用轉換為派生類的指針或引用
(3)typeid 函數
該函數的主要作用就是讓用戶知道當前的變量是什麼類型的,比如使用typid(a).name()就能知道變量a是什麼類型的。因為typid()函數是一個反回類型為const typid_info&類型的函數,所以下面先對type_info類作下介紹
(4)type_info類
該類的具體實現方式依編譯器而定,但一般都有如下的成員定義
class type_info
{private:
type_info(const type_info &);
type_info& operator =(const type_info&); //type_info類的復制構造函數和賦值運算符是私有的。
public:
virtual ~type_info(); //析構函數
bool operator = =(const type_info&) const; //在type_info類中重載了= =運算符,該運算符可以比較兩個對象的類型是否相等。
bool operator !=(const type_info&)const; //重載的!=運算符,以比較兩個對象的類型是否不相等
const char * name() const; //使用得較多的成員函數name,該函數反回對象的類型的名字。前面使用的typeid(a).name()就調用了該成員函數
bool before(const type_info&);};
因為type_info類的復制構造函數和賦值運算符都是私有的,所以不允許用戶自已創建type_info的類,比如type_info A;錯誤,沒有默認的構造函數。唯一要使用type_info類的方法就是使用typeid函數。
(5)typeid的使用方式
1)使用type_info類中的name()成員函數反回對象的類型的名稱。其方法為:typeid(object).name()其中object是要顯示的對象的類型名,該函數反回的名字因編譯器而定。這裡要注意的就是使用方式一中提到的虛函數類型的問題,即如果有類A,且有虛函數,類B,C,D都是從類A派生的,且都重定義了類A中的虛函數,這時有類A的指針p,再把對象類B的對象的地址賦給指針p,則typeid(p).name()將反回的類型將是A*,因為這裡的p表示的是一個指針,該指針是類型為A的指針,所以反回A*,而typeid(*p).name()將反回B,因為指針p是指向類B的對象的,而*p就表示的是類B的對象的類型,所以反回B。
2)使用type_info類中重載的= =與!=比較兩個對象的類型是否相等。使用該方法需要調用類type_info中重載的= =和!=操作符,其使用方法為typid(object1)= =typid(object2);如果兩個對象的類型相等則反回1,如果不相等則為0。這種使用方法通常用於比較兩個帶有虛函數的類的對象是否相等,比如有類A,其中定義有虛函數,而類B,類C,類D,都是從類A派生而來的且重定義了該虛函數,這時有個類A的指針p和p1,按照虛函數的原理,基類的指針可以指向任何派生類的對象,在這時就有可能需要比較兩個指針是否指向同一個對象,這時就可以這樣使用typeid了,typeid(*p)= =typeid(*p1);這裡要注意的是typeid(*p)與typeid(p)是指的不同的對象類型,typeid(p)表示的是p的類型,在這裡p是一個指針,這個指針指向的是類A的對象,所以p的類型是A*,而typeid(*p)則不一樣,*p表示的是指針p實際所指的對象的類型,比如這裡的指針p指向派生類B,則typeid(*p)的類型為B。所以在測試兩個指針的類型是否是相等時應使用*p,即typeid(*p)= =typeid(*p1)。如果是typeid(p)= =typeid(p1)的話,則無論指針p和p1指向的什麼派生類對象,他們都是相等的,因為都是A *的類型。
3.C++的四種類型轉換
(1)dynamic_cast
1)dynamic_cast只能將指向派生類對象的基類指針或引用轉換為派生類的指針或引用,若用於其他轉換則指針為空,引用則拋出異常。此為向下類型轉換。
2)dynamic_cast轉換符只能用於指針或者引用。dynamic_cast轉換符只能用於含有虛函數的類。dynamic_cast轉換操作符在執行類型轉換時首先將檢查能否成功轉換,如果能成功轉換則轉換之,如果轉換失敗,如果是指針則反回一個0值,如果是轉換的是引用,則拋出一個bad_cast異常,所以在使用dynamic_cast轉換之間應使用if語句對其轉換成功與否進行測試,比如pd=dynamic_cast<D*>(pb); if(pd){…}else{…},或者try{};catch(bad_cast){}
3)使用 typeid 要注意一個問題,那就是某些編譯器(如 Visual C++)默認狀態是禁用 RTTI 的,目的是消除性能上的開銷。如果你的程序確實使用了 RTTI,一定要記住在編譯前啟用 RTTI。(vc6.0啟用方式:project->setting->c/c++->category->c++ Language 下面第二個復選框選中)。使用 typeid 可能產生一些將來的維護問題。假設你決定擴展上述的類層次,從MediaFile 派生另一個叫 LocalizeMedia 的類,用這個類表示帶有不同語言說明文字的媒體文件。但 LocalizeMedia 本質上還是個 MediaFile 類型的文件。因此,當用戶在該類文件圖標上單擊右鍵時,文件管理器必須提供一個“播放”菜單。可惜 build()成員函數會調用失敗,原因是你沒有檢查這種特定的文件類型。為了解決這個問題,你必須象下面這樣對 build() 打補丁:
void menu::build(const File * pfile)
{
//......
else if (typeid(*pfile)==typeid(LocalizedMedia))
{
add_option("play");
}
}
唉,這種做法真是顯得太業余了,以後每次添加新的類,毫無疑問都必須打類似的補丁。顯然,這不是一個理想的解決方案。這個時候我們就要用到 dynamic_cast,這個運算符用於多態編程中保證在運行時發生正確的轉換(即編譯器無法驗證是否發生正確的轉換)。用它來確定某個對象是 MediaFile 對象還是它的派生類對象。dynamic_cast 常用於從多態編程基類指針向派生類指針的向下類型轉換。它有兩個參數:一個是類型名;另一個是多態對象的指針或引用。其功能是在運行時將對象強制轉換為目標類型並返回布爾型結果。也就是說,如果該函數成功地並且是動態的將 *pfile 強制轉換為 MediaFile,那麼 pfile的動態類型是 MediaFile 或者是它的派生類。否則,pfile 則為其它的類型:
void menu::build(const File * pfile)
{
if (dynamic_cast <MediaFile *> (pfile))
{
// pfile 是 MediaFile 或者是MediaFile的派生類 LocalizedMedia
add_option("play");
}
else if (dynamic_cast <TextFile*> (pfile))
{
// pfile 是 TextFile 是TextFile的派生類
add_option("edit");
}
}
細細想一下,雖然使用 dynamic_cast 確實很好地解決了我們的問題,但也需要我們付出代價,那就是與 typeid 相比,dynamic_cast 不是一個常量時間的操作。為了確定是否能完成強制類型轉換,dynamic_cast`必須在運行時進行一些轉換細節操作。因此在使用 dynamic_cast 操作時,應該權衡對性能的影響。
(2)static_cast
1)完成向上類型轉換,即將指向派生類的指針或引用轉換為指向同一層次中的一個基類的指針或引用。
2) 使用例子
[cpp]
#include <iostream>
class B
{
int i;
public: // Conversion constructor.
B(int a) : i(a) { }
void display()
{
std::cout << i;
}
};
int main()
{
B bobj1 = (B)123; // C-style cast int to B.
bobj1.display();
std::cout << '/';
bobj2 = B(456); // Constructor notation B
bobj2.display();
std::cout << '/';
B bobj3 = static_cast<B>(789); // Static_cast.
bobj3.display();
return 0;
}
(3)reinterpret_cast
reinterpret_cast操作符代替了大多數其它C風格類型轉換的使用。reinterpret_cast將指針轉換為其它指針類型、將數字轉換為指針或將指針轉換為數字。與使用C風格的類型轉換一樣,當使用reinterpret_cast操作符時,用戶應該知道自已要干什麼。有時,只有C風格的類型轉換才能起作用,但這並不是說從來都不應該使用reinterpret_cast。下例展示了一個用void型指針返回100個字符的緩沖區的簡單內存分配程序。Main()函數將返回結果賦值給一個字符型指針。C++的轉換規則與C並不相同。在C++的轉換規則中,不能隱式地將void*轉換為char*,因此,需要進行類型轉換。下面使用了reinterpret_cast而不是C語言風格的類型轉換。
[cpp]
#include <iostream>
#include <cstring>
//Create a buffer. //
void* getmem()
{
static char buf[100];
return buf;
}
int main()
{
char* cp = reinterpret_cast<char*>(getmem());
strcpy(cp, "Hello, Woody");
std::cout << cp;
return 0;
}
(4)const_cast
剛才所討論的3種類型轉換操作都沒有涉及“常量屬性”,即不能使用它們移去對象的常量屬性。為此,我們要使用const_cast操作符。除了const和volatile關鍵字之外,它的類型參數必須和對象參數的類型相匹配。
例如,考慮一個全局的計數器,它描述了對常類對象或其它類型的類對象的操作次數。下面給出了這樣的一個例子
[cpp
#include <iostream>
class A
{
int val;
int rptct; // Number of times the object is reported.
public:
A(int v) : val(v), rptct(0) { }
~A()
{
cout << val << " was reported " << rptct << " times.";
}
void report() const;
};
void A::report() const
{
const_cast<A*>(this)->rptct++;
cout << val << endl;
}
int main()
{
const A a(123);
a.report();
a.report();
a.report();
return 0;
}