c++中的類型識別

  • 2020 年 3 月 13 日
  • 筆記

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 必須多態實現。