c++中的型別識別
1、型別識別的相關概念
(1)型別識別的作用
型別識別是面向物件中引入的一個新概念,主要用來判斷賦值相容性原則中的型別問題,即此時的資料型別到底是基類型別還是派生類型別?
當基類指標指向子類物件 或者 基類引用成為子類物件的別名 時,就需要使用型別識別;
1 Base *p = new Derived(); 2 Base &r = *p
對於上面的語句,我們可以這樣認識,指標p是Base型別,但是P 又指向了一個新的Derived型別,此時很難判斷指標P 的資料型別;同理,引用r 本來作為父類的別名而存在,但由於賦值相容性,引用r也可以作為子類的別名,同樣此時 引用 r 的資料型別也不能確定;
注:1)由之前所學知識,若沒有虛擬函式重寫,編譯器為了安全起見,會將指標p 當作 Base 型別;(編譯期間)
2)若有虛擬函式重寫,就會發生動態多型特性,此時就會根據指標p 所指向的具體資料型別來確定指標p 的資料型別。(執行期間)
(2)型別識別的分類
1)靜態型別:變數(物件)自身的型別;在編譯階段就能確定所使用變數的資料型別。
2)動態型別:指標(引用)所指向物件的實際型別;在執行階段根據指標所指向的具體資料型別來確定所使用的資料型別。
Base *b 所指向的實際物件無法確定,若指標b 指向的是子類物件,則程式正常執行;若指標b 指向的是父類物件,則程式有可能出現 Bug;
注:在 g++ 編譯器下上述情況均可正常執行,但後者不建議使用;
在賦值相容原則中,基類指標是否可以強制型別轉換為子類指標取決於動態型別;(很重要!!!)--- 只有動態型別是子類物件才能進行合法轉換
2、如何得到動態型別
(1)利用多型
1)必須從基類開始提供型別虛擬函式;
2)所有的派生類都必須重寫型別虛擬函式;
3)每個派生類的型別 ID必須唯一;
結果:呼叫型別虛擬函式就可以知道當前的物件究竟是什麼型別,這樣就可以得到動態型別,達到動態型別識別效果;
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 class Base 7 { 8 public: 9 enum { ID = 0 }; 10 11 virtual int type() // 型別虛擬函式 12 { 13 return ID; 14 } 15 }; 16 17 class Derived : public Base 18 { 19 public: 20 enum { ID = 1 }; 21 22 int type() 23 { 24 return ID; 25 } 26 27 void print() 28 { 29 cout << "I'm a Derived. " << endl; 30 } 31 }; 32 33 class Child : public Base 34 { 35 public: 36 enum { ID = 2 }; 37 38 int type() 39 { 40 return ID; 41 } 42 }; 43 44 void test(Base* pb) 45 { 46 if( pb->type() == Child::ID ) 47 { 48 Child* pc = static_cast<Child*>(pb); 49 //Child* pc = dynamic_cast<Child*>(pb); // 同上 50 51 cout << "& = " << pc << endl; 52 cout << "I'm a Child. " << endl; 53 } 54 55 if( pb->type() == Derived::ID ) 56 { 57 Derived* pd = static_cast<Derived*>(pb); 58 //Derived* pd = dynamic_cast<Derived*>(pb); // 同上 59 60 cout << "& = " << pd << endl; 61 pd->print(); 62 } 63 64 if( pb->type() == Base::ID ) 65 { 66 cout << "& = " << pb << endl; 67 cout << "I'm a Base. " << endl; 68 } 69 } 70 71 int main(int argc, char *argv[]) 72 { 73 Base b; 74 Derived d; 75 Child c; 76 77 test(&b); 78 test(&d); 79 test(&c); 80 81 return 0; 82 } 83 /** 84 * 執行結果: 85 * & = 0x7ffccf0dd850 86 * I'm a Base. 87 * & = 0x7ffccf0dd860 88 * I'm a Derived. 89 * & = 0x7ffccf0dd870 90 * I'm a Child. 91 */
(2)利用 dynamic_cast
1)dynamic_cast這個關鍵字如果要轉換的實際型別和指定的型別不一樣,則會返回NULL。例如當指定型別為子類物件時,如果父類指標的動態型別是這個子類物件時,轉換成功,而動態型別是父類物件或者其他子類物件時,轉換失敗;
2)dynamic_cast 要求使用的目標物件型別必須是多型,即:所在類族至少有一個虛擬函式;
3)只能用於指標和引用之間的轉換
1. 用於指標轉換時,轉換失敗,返回空指標;
2. 用於引用轉換時,轉換失敗,將引發 bad_cast異常。
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 class Base 7 { 8 public: 9 virtual ~Base() 10 { 11 12 } 13 }; 14 15 class Derived : public Base 16 { 17 public: 18 void print() 19 { 20 cout << "I'm a Derived. " << endl; 21 } 22 }; 23 24 class Child : public Base 25 { 26 27 }; 28 29 void test(Base* pb) 30 { 31 // dynamic_cast 只能確定最終的轉化結果,無法獲取動態型別的原型 32 Derived* pd = dynamic_cast<Derived*>(pb); 33 34 if(pd != NULL) 35 { 36 // Derived 類型別, 可以使用指標pd訪問Derived類的成員 37 cout << "& = " << pd << endl; 38 pd->print(); 39 } 40 else 41 { 42 Child* pc = dynamic_cast<Child*>(pb); 43 44 if(pc != NULL) 45 { 46 // Child 類型別, 可以使用指標pc訪問Child類的成員 47 cout << "& = " << pc << endl; 48 cout << "I'm a Child. " << endl; 49 } 50 else 51 { 52 // Base 類型別, 可以使用指標pb訪問Base類的成員 53 cout << "& = " << pc << endl; 54 cout << "I'm a Base. " << endl; 55 } 56 } 57 } 58 59 int main(int argc, char *argv[]) 60 { 61 Base b; 62 Derived d; 63 Child c; 64 65 test(&b); 66 test(&d); 67 test(&c); 68 69 return 0; 70 } 71 /** 72 * 執行結果: 73 * & = 0 74 * I'm a Base. 75 * & = 0x7ffccf0dd860 76 * I'm a Derived. 77 * & = 0x7ffccf0dd870 78 * I'm a Child. 79 */利用 dynamic_cast 實現型別識別
(3)利用 typeid(推薦這種方法)
1)typeid 是一個關鍵字,專門用於動態型別識別;
2)typeid 關鍵字返回對應引數的型別資訊,此型別資訊是一個type_info類物件;
1. 當引數為型別時,返回靜態型別資訊;
2. 當引數為變數時:1> 引數變數內部不存在虛擬函式表時,返回靜態型別資訊; 2> 引數變數內部存在虛擬函式表時,返回動態型別資訊;
3. 當引數為 NULL 時,將丟擲異常;
3)typeid 使用時需要包含標頭檔案<typeinfo>;
4)typeid 使用時直接指定物件或者型別。
5)typeid 在不同的編譯器內部實現是不同的;
1 int i = 0; 2 3 const type_info& tiv = typeid(i); // 將 i 的型別資訊放到 type_info 中去; 4 const type_info& tii = typeid(int); 5 6 cout << (tiv == tii) << endl; // 1
1 #include <iostream> 2 #include <string> 3 #include <typeinfo> 4 5 using namespace std; 6 7 class Base 8 { 9 public: 10 virtual ~Base() 11 { 12 } 13 }; 14 15 class Derived : public Base 16 { 17 public: 18 void print() 19 { 20 cout << "I'm a Derived." << endl; 21 } 22 }; 23 24 class Child : public Base 25 { 26 public: 27 void print() 28 { 29 cout << "I'm a Child." << endl; 30 } 31 }; 32 33 void test(Base* pb) 34 { 35 const type_info& tb = typeid(*pb); 36 37 if( tb == typeid(Derived) ) 38 { 39 Derived* pd = dynamic_cast<Derived*>(pb); 40 41 cout << "& = " << pd << endl; 42 pd->print(); 43 } 44 else if( tb == typeid(Child) ) 45 { 46 Child* pc = dynamic_cast<Child*>(pb); 47 48 cout << "& = " << pc << endl; 49 pc->print(); 50 51 } 52 else if( tb == typeid(Base) ) 53 { 54 cout << "& = " << pb << endl; 55 cout << "I'm a Base. " << endl; 56 } 57 58 cout << tb.name() << endl; 59 } 60 61 int main(int argc, char *argv[]) 62 { 63 Base b; 64 Derived d; 65 Child c; 66 int index; 67 char ch; 68 69 const type_info& tp = typeid(b); 70 const type_info& tc = typeid(d); 71 const type_info& tn = typeid(c); 72 const type_info& ti = typeid(index); 73 const type_info& tch = typeid(ch); 74 75 cout<<tp.name()<<endl; 76 cout<<tc.name()<<endl; 77 cout<<tn.name()<<endl; 78 cout<<ti.name()<<endl; 79 cout<<tch.name()<<endl; 80 81 test(&b); 82 test(&d); 83 test(&c); 84 85 return 0; 86 } 87 /** 88 * 執行結果: 89 * 4Base 90 * 7Derived 91 * 5Child 92 * i 93 * c 94 * & = 0x7ffcbd4d6280 95 * I'm a Base. 96 * 4Base 97 * & = 0x7ffcbd4d6290 98 * I'm a Derived. 99 * 7Derived 100 * & = 0x7ffcbd4d62a0 101 * I'm a Child. 102 * 5Child 103 */利用 typeid 實現型別識別
3 種動態型別的實現方法 建議選 第3種 (typeid)。
對於多型實現,存在以下缺陷:
1)必須從基類開始提供型別虛擬函式;
2)所有的派生類都必須重寫型別虛擬函式;
3)每個派生類的型別名必須唯一;
對於 dynamic_cast 實現,只能得到型別轉換的結果,不能獲取真正的動態型別,同時 dynamic_cast 必須多型實現。
&n