typeid 運算符用來獲取一個表達式的類型信息。類型信息對於編程語言非常重要,它描述了數據的各種屬性:
-
對於基本類型(int、float 等C++內置類型)的數據,類型信息所包含的內容比較簡單,主要是指數據的類型。
-
對於類類型的數據(也就是對象),類型信息是指對象所屬的類、所包含的成員、所在的繼承關系等。
類型信息是創建數據的模板,數據占用多大內存、能進行什麼樣的操作、該如何操作等,這些都由它的類型信息決定。
typeid 的操作對象既可以是表達式,也可以是數據類型,下面是它的兩種使用方法:
typeid( dataType )
typeid( expression )
dataType 是數據類型,expression 是表達式,這和 sizeof 運算符非常類似,只不過 sizeof 有時候可以省略括號
( )
,而 typeid 必須帶上括號。
typeid 會把獲取到的類型信息保存到一個 type_info 類型的對象裡面,並返回該對象的常引用;當需要具體的類型信息時,可以通過成員函數來提取。typeid 的使用非常靈活,請看下面的例子(只能在 VC/VS 下運行):
#include <iostream>
#include <typeinfo>
using namespace std;
class Base{ };
struct STU{ };
int main(){
//獲取一個普通變量的類型信息
int n = 100;
const type_info &nInfo = typeid(n);
cout<<nInfo.name()<<" | "<<nInfo.raw_name()<<" | "<<nInfo.hash_code()<<endl;
//獲取一個字面量的類型信息
const type_info &dInfo = typeid(25.65);
cout<<dInfo.name()<<" | "<<dInfo.raw_name()<<" | "<<dInfo.hash_code()<<endl;
//獲取一個對象的類型信息
Base obj;
const type_info &objInfo = typeid(obj);
cout<<objInfo.name()<<" | "<<objInfo.raw_name()<<" | "<<objInfo.hash_code()<<endl;
//獲取一個類的類型信息
const type_info &baseInfo = typeid(Base);
cout<<baseInfo.name()<<" | "<<baseInfo.raw_name()<<" | "<<baseInfo.hash_code()<<endl;
//獲取一個結構體的類型信息
const type_info &stuInfo = typeid(struct STU);
cout<<stuInfo.name()<<" | "<<stuInfo.raw_name()<<" | "<<stuInfo.hash_code()<<endl;
//獲取一個普通類型的類型信息
const type_info &charInfo = typeid(char);
cout<<charInfo.name()<<" | "<<charInfo.raw_name()<<" | "<<charInfo.hash_code()<<endl;
//獲取一個表達式的類型信息
const type_info &expInfo = typeid(20 * 45 / 4.5);
cout<<expInfo.name()<<" | "<<expInfo.raw_name()<<" | "<<expInfo.hash_code()<<endl;
return 0;
}
運行結果:
int | .H | 529034928
double | .N | 667332678
class Base | .?AVBase@@ | 1035034353
class Base | .?AVBase@@ | 1035034353
struct STU | .?AUSTU@@ | 734635517
char | .D | 4140304029
double | .N | 667332678
從本例可以看出,typeid 的使用非常靈活,它的操作數可以是普通變量、對象、內置類型(int、float等)、自定義類型(結構體和類),還可以是一個表達式。
本例中還用到了 type_info 類的幾個成員函數,下面是對它們的介紹:
-
name() 用來返回類型的名稱。
-
raw_name() 用來返回名字編碼(Name Mangling)算法產生的新名稱。關於名字編碼的概念,我們已在《C++函數編譯原理和成員函數的實現》中講到。
-
hash_code() 用來返回當前類型對應的 hash 值。hash 值是一個可以用來標志當前類型的整數,有點類似學生的學號、公民的身份證號、銀行卡號等。不過 hash 值有賴於編譯器的實現,在不同的編譯器下可能會有不同的整數,但它們都能唯一地標識某個類型。
遺憾的是,C++ 標准只對 type_info 類做了很有限的規定,不僅成員函數少,功能弱,而且各個平台的實現不一致。例如上面代碼中的 name() 函數,
nInfo.name()
、
objInfo.name()
在 VC/VS 下的輸出結果分別是
int
和
class Base
,而在 GCC 下的輸出結果分別是
i
和
4Base
。
C++ 標准規定,type_info 類至少要有如下所示的 4 個 public 屬性的成員函數,其他的擴展函數編譯器開發者可以自由發揮,不做限制。
1) 原型:const char* name() const;
返回一個能表示類型名稱的字符串。但是C++標准並沒有規定這個字符串是什麼形式的,例如對於上面的
objInfo.name()
語句,VC/VS 下返回“class Base”,但 GCC 下返回“4Base”。
2) 原型:bool before (const type_info& rhs) const;
判斷一個類型是否位於另一個類型的前面,rhs 參數是一個 type_info 對象的引用。但是C++標准並沒有規定類型的排列順序,不同的編譯器有不同的排列規則,程序員也可以自定義。要特別注意的是,這個排列順序和繼承順序沒有關系,基類並不一定位於派生類的前面。
3) 原型:bool operator== (const type_info& rhs) const;
重載運算符“==”,判斷兩個類型是否相同,rhs 參數是一個 type_info 對象的引用。
4) 原型:bool operator!= (const type_info& rhs) const;
重載運算符“!=”,判斷兩個類型是否不同,rhs 參數是一個 type_info 對象的引用。
關於運算符重載,我們將在《C++運算符重載》一章中詳細講解。
raw_name() 是 VC/VS 獨有的一個成員函數,hash_code() 在 VC/VS 和較新的 GCC 下有效。
可以發現,不像 Java、C# 等動態性較強的語言,C++ 能獲取到的類型信息非常有限,也沒有統一的標准,如同“雞肋”一般,大部分情況下我們只是使用重載過的“==”運算符來判斷兩個類型是否相同。
判斷類型是否相等
typeid 運算符經常被用來判斷兩個類型是否相等。
1) 內置類型的比較
例如有下面的定義:
char *str;
int a = 2;
int b = 10;
float f;
類型判斷結果為:
類型比較 |
結果 |
類型比較 |
結果 |
typeid(int) == typeid(int)
true
typeid(int) == typeid(char)
false
typeid(char*) == typeid(char)
false
typeid(str) == typeid(char*)
true
typeid(a) == typeid(int)
true
typeid(b) == typeid(int)
true
typeid(a) == typeid(a)
true
typeid(a) == typeid(b)
true
typeid(a) == typeid(f)
false
typeid(a/b) == typeid(int)
true
typeid 返回 type_info 對象的引用,而表達式
typeid(a) == typeid(b)
的結果為 true,可以說明,一個類型不管使用了多少次,編譯器都只為它創建一個對象,所有 typeid 都返回這個對象的引用。
需要提醒的是,為了減小編譯後文件的體積,編譯器不會為所有的類型創建 type_info 對象,只會為使用了 typeid 運算符的類型創建。不過有一種特殊情況,就是帶虛函數的類(包括繼承來的),不管有沒有使用 typeid 運算符,編譯器都會為帶虛函數的類創建 type_info 對象,我們將在《C++ RTTI機制(運行時類型識別)》中展開講解。
2) 類的比較
例如有下面的定義:
class Base{};
class Derived: public Base{};
Base obj1;
Base *p1;
Derived obj2;
Derived *p2 = new Derived;
p1 = p2;
類型判斷結果為:
類型比較 |
結果 |
類型比較 |
結果 |
typeid(obj1) == typeid(p1)
false
typeid(obj1) == typeid(*p1)
true
typeid(&obj1) == typeid(p1)
true
typeid(obj1) == typeid(obj2)
false
typeid(obj1) == typeid(Base)
true
typeid(*p1) == typeid(Base)
true
typeid(p1) == typeid(Base*)
true
typeid(p1) == typeid(Derived*)
false
表達式
typeid(*p1) == typeid(Base)
和
typeid(p1) == typeid(Base*)
的結果為 true 可以說明:即使將派生類指針 p2 賦值給基類指針 p1,p1 的類型仍然為 Base*。
type_info 類的聲明
最後我們再來看一下 type_info 類的聲明,以進一步了解它所包含的成員函數以及這些函數的訪問權限。type_info 類位於
typeinfo
頭文件,聲明形式類似於:
class type_info {
public:
virtual ~type_info();
int operator==(const type_info& rhs) const;
int operator!=(const type_info& rhs) const;
int before(const type_info& rhs) const;
const char* name() const;
const char* raw_name() const;
private:
void *_m_data;
char _m_d_name[1];
type_info(const type_info& rhs);
type_info& operator=(const type_info& rhs);
};
它的構造函數是 private 屬性的,所以不能在代碼中直接實例化,只能由編譯器在內部實例化(借助友元)。而且還重載了“=”運算符,也是 private 屬性的,所以也不能賦值。