MFC原理第三講.RTTI運行時類型識別
一丶什么是RTTI
RTTI. 運行時的時候類型的識別. 運行時類型信息程序.能夠使用基類(父類)指針 或者引用 來檢查這些指針或者引用所指的對象. 實際派生的類型
簡單來說就是 使用父類指針檢查這個對象是屬於哪個類.
1.本篇博客需要弄清的問題
1.1 MFC為什么要構建RTTI
1.2 DECLARE_DYNAMIC 宏
1.3 IMPLEMENT_DYNAMIC 宏
1.4 RUNTIME_CLASS 宏
1.5 CRuntime Class 結構體
2.簡單了解關鍵字的使用
1.類中定義的static的變量的作用以及怎么初始化.
2.類中定義的const的變量的作用以及怎么初始化
3.類中的 static + const定義的變量的作用以及怎么初始化.
二丶C++簡單的RTTI運行類型識別
在講解我們要搞清楚的問題的時候.寫一個簡單的小例子. 使用C++自帶的 編譯時的RTTI程序. 注意是編譯時.
具體做法:
1. 要使用typeid 關鍵字 typeid(對象) typeid (類名)
2.首先要包含頭文件 <typeinfo.h>
3.啟動C++自己的類型. 屬性 -> 配置屬性 -> C/C++ -> 語言 -> 啟用運行時類型信息 命令行加的是 /GR 我們也可以直接加/GR
例如下圖:
1. 首先我們創建兩個類. 一個是 CDog 一個是CCat.
2.然后我們的Main函數 定義 CDog 對象以及CCat對象. 並且判斷 CDog對象是否屬於CDog這個類.
3.如果屬於我們則進行打印.否則相反.
具體代碼:
#include <stdio.h> #include <typeinfo.h> #include "Dog.h" #include "Cat.h" int main(int argc, char *argv[]) { CDog dog; CCat cat; if (typeid(dog) == typeid(CDog)) 判斷dog對象是否屬於 CDog類 主要就是這行代碼 { printf("是屬於這個對象\r\n"); } else { printf("不是屬於這個對象\r\n"); } system("pause"); }
實現應用截圖:
這個就是簡單的RTTI運行時類型識別了.
三丶類中的 static關鍵字.const關鍵字 static + const 關鍵字的作用以及初始化
1.static關鍵字 修飾的變量.外部進行初始化.並且不依賴與對象.也就是說直接 類名::變量 則可以調用 類型 類名::成員變量 = 值;
2.const關鍵字修飾的變量只能讀不能改. 初始化的時候必須在 類的構造的初始化列表進行初始化.
3.static + const修飾的變量. 只能讀不能改. 類名直接調用. 初始化的時候在外部進行初始化 const 類型 類名::成員變量 = 值;
四丶MFC為什么自己構建RTTI
MFC因為出現的年代比較早.所以自己實現了RTTI. 而且依靠的就是兩個宏
1.2 的 DECLARE_DYNAMIC 宏
1.3 的 IMPLEMENT_DYNAMIC 宏
其中1.3里面的宏也包含了一個關鍵的宏 RUNTIME_CLASS 以及關鍵結構體 CRuntime Class
一丶使用1.2 宏 1.3宏.
現在我們要讓我們自己的類擁有RTTI運行時類型識別.
步驟:
1.我們的自己的WinApp 類里面 定義 DECLARE_DYNAMIC宏
2.main函數之前使用 IMPLEMENT_DYNAMIC 宏
3.使用 IsKindOf(CRuntime Class *) 來判斷是否是繼承父類. 因為是 CRuntime Class * 所以 我們要使用宏包含我們的 父類 RUNTIME_CLASS(父類)
4.如果是繼承父類 則返回1. 否則 返回0
知道步驟了.那么打開第一篇博客的代碼. 自己實現的窗口程序.去編寫.
1.自己類里面定義DECLARE_DYNAMIC宏 截圖:
2.自己類的實現文件中 定義IMPLEMENT_DYNAMIC(自己的類名,父類類名)
3.自己的類已經擁有了Rtti 類型識別.使用Rtii 運行識別.
二丶 RUNTIME_CLASS 宏解析
上面我們使用 RUNTIME_CLASS 宏來做使用那么我們看一下MFC怎么定義的.
#define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
其實也是一個宏.不過我們可以拆解一下.
#define _RUNTIME_CLASS(CWinApp) ((CRuntimeClass*)(&CWinApp::classCWinApp))
其中 ##代表了鏈接的意思 也就是 Class##ClassName 可以變成 ClassWinApp
所以上面我們用宏寫的代碼下方可以替換成我們解析出來的宏.
返回了一個 CRuntime Class * . 返回的是CWinAPP里面的一個成員的地址.因為前邊有一個取地址符號..
首先看一下CRuntimeClass結構體吧
三丶CRuntimeClass結構體
struct CRuntimeClass { // Attributes LPCSTR m_lpszClassName; 類名 int m_nObjectSize; 類的大小 UINT m_wSchema; 加載類的編號 CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class #ifdef _AFXDLL CRuntimeClass* (PASCAL* m_pfnGetBaseClass)(); #else CRuntimeClass* m_pBaseClass; #endif // Operations CObject* CreateObject(); BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const; 判斷函數 // dynamic name lookup and creation static CRuntimeClass* PASCAL FromName(LPCSTR lpszClassName); static CRuntimeClass* PASCAL FromName(LPCWSTR lpszClassName); static CObject* PASCAL CreateObject(LPCSTR lpszClassName); static CObject* PASCAL CreateObject(LPCWSTR lpszClassName); // Implementation void Store(CArchive& ar) const; static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum); // CRuntimeClass objects linked together in simple list CRuntimeClass* m_pNextClass; // 執向下一個CRunTimeClass const AFX_CLASSINIT* m_pClassInit; };
因為上面成員較多.下方簡化一下.
struct CRuntimeClass { // Attributes LPCSTR m_lpszClassName; 類名 int m_nObjectSize; 類的大小 UINT m_wSchema; 加載類的編號 ... BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const; 判斷函數是否是父類 ... CRuntimeClass* m_pNextClass; // 執向下一個CRunTimeClass };
這個是一個鏈表. 是記錄類型的一個結構. 因為是鏈表.所以可以進行檢查.
四丶DECLARE_DYNAMIC 宏解析
其實 DECLARE_DYNAMIC 宏也是一個文字替換的東西.我們可以看下代碼.
#define DECLARE_DYNAMIC(class_name) \ public: \ static const CRuntimeClass class##class_name; \ virtual CRuntimeClass* GetRuntimeClass() const; \ 替換后是下邊的代碼. public: static const CRuntimeClass classCMyApp; virtual CRuntimeClass* GetRuntimeClass() const;
在我們的CMyAPP 里面的宏則可以直接去掉了.
做的事情;
1.申明了 static const 全局可讀的變量. 也就是一個 類型記錄信息結構體 CRuntimeClass 結構
問題: 既然是static const定義的.那么肯定是在外面進行初始化的.也就是我們的
IMPLEMENT_DYNAMIC 宏.而這個宏內部還包含了宏.並且對我們添加了默認參數.
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \ AFX_COMDAT const CRuntimeClass class_name::class##class_name = { \ #class_name, sizeof(class class_name), wSchema, pfnNew, \ RUNTIME_CLASS(base_class_name), NULL, class_init }; \ CRuntimeClass* class_name::GetRuntimeClass() const \ { return RUNTIME_CLASS(class_name); }
我們簡化一下. 其實也是一個文字替換游戲.
const CRuntimeClass CMyApp::classCMyApp = { "CMyApp", sizeof(class CMyApp), NULL, NULL, RUNTIME_CLASS(CWinApp), NULL, NULL }; CRuntimeClass* CMyApp::GetRuntimeClass() const { return RUNTIME_CLASS(CMyApp); }
其實就是對我們的static + const 變量進行初始化.
上圖內部還有一個RUNTIME_CLASS 我們因為上面解析過了RUNTIME_CLASS 知道了.其實是獲取 CWinAPP::classCWinapp 成員.
所以可以繼續進行替換.
替換完如下.
const CRuntimeClass CMyApp::classCMyApp = { "CMyApp", sizeof(class CMyApp), NULL, NULL, (CRuntimeClass*)(&CWinApp::classCWinApp), NULL, NULL }; CRuntimeClass* CMyApp::GetRuntimeClass() const { return(CRuntimeClass*)(&CMyApp::classCWinApp); }
代碼截圖:
我們的IMPLEMENT_DYNAMIC宏就等價於下面的代碼了.
1. 初始化類內部的 CRuntimeClass 結構的成員.
2.添加了一個獲取父類的ClassCWinAPP的成員了.
五丶RTTI總結
根據第四小節.我們已經把所有宏的本身模樣還原出來了.看的雜亂無章.但是總結一下很簡單.
1. 首先有一個結構叫做CRuntimeClass. 里面存儲了類型說明. 比如類名稱.大小. 以及判斷是否是父類....
2. 有一個宏叫做 DECLARE_DYNAMIC宏. 這個宏就是定義了一個 自己的一個CRuntimeClass 結構的成員.並且添加了一個獲取自己這個成員的一個虛函數.
3. 實現宏IMPLEMENT_DYNAMIC 其實就是對DECLARE_DYNAMIC 中定義的CRuntimeClass成員進行初始化. 並且實現了 獲取自己這個成員的虛函數.
六丶RTTI中運行時類型識別的方法解析
上方我們講了RTTI 以及CRuntimeClass 以及兩個宏的總結. 那么我們要使用就是使用 isKindOf來使用. 那么解析下這個函數怎么使用了.
1.取出我們自己當前對象的 ClassCMyWinApp 指針.
2.循環遍歷是否等於父類
3.不等於父類則進行遍歷. 因為CRuntimeClass是一個鏈表結構. 一直進行遍歷.地址比較.如果是則返回True
因為代碼截圖比教麻煩.所以直接貼里面的核定代碼了.
BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const { .... CRuntimeClass* pClassThis = GetRuntimeClass(); 獲取自己的CRuntimeClass 成員 ... return pClassThis->IsDerivedFrom(pClass); 進行判斷. } BOOL CRuntimeClass::IsDerivedFrom(const CRuntimeClass* pBaseClass) const { 其余代碼省略 const CRuntimeClass* pClassThis = this; 得到字節的CRuntimeClass成員 while (pClassThis != NULL) 循環遍歷不是NULL { if (pClassThis == pBaseClass) 如果是父類則返回TRUE return TRUE; pClassThis = pClassThis->m_pBaseClass; 否則等於父類指針.繼續遍歷判斷 } return FALSE; // 沒有返回FALSE }
七丶編寫代碼打印輸出父類信息.以及繼承關系
既然我們自己類的內部存儲着CRuntimeClass *的指針. 而且是一個鏈表. 它父類也有自己的. 那么完全可以進行遍歷.打印出父類的信息.
實現思路:
1.獲取自己的CRuntimeClass *指針
2.遍歷CRuntimeClass 成員是否為NULL
3.不為NULL 依次打印出結構體的內容
4. 當前的CRunTimeClass 指針修改為父類的CRuntimeClass * 指針
具體代碼實現:
void CMyApp::PrintParentRuntimeInfo() { //1.獲取自己當前的CRuntimeClass信息. 進行遍歷. CRuntimeClass *pCurClass = GetRuntimeClass(); //進行循環 CString str; while (NULL != pCurClass) { str = ""; str.Format(TEXT("類的名稱 = %s \r\n"),pCurClass->m_lpszClassName); //類的名稱 OutputDebugString(str); str = ""; str.Format(TEXT("類的大小 = %d \r\n"),pCurClass->m_nObjectSize); OutputDebugString(str); str = ""; str.Format(TEXT("類的編號%d \r\n"), pCurClass->m_wSchema); OutputDebugString(str); str = ""; str.Format(TEXT("類的父類指針 %p \r\n"), pCurClass->m_pBaseClass); OutputDebugString(str); if (pCurClass->m_pBaseClass != nullptr) { str = ""; str.Format(TEXT("類的父類名稱 %s \r\n"), pCurClass->m_pBaseClass->m_lpszClassName); OutputDebugString(str); } str = TEXT("--------------------------------\r\n"); OutputDebugString(str); pCurClass = pCurClass->m_pBaseClass; //一直遍歷到頂層位置 } }
在Ininstance里面調用即可.
實現結果截圖: 使用DebugView獲取信息.
課堂代碼: 鏈接:https://pan.baidu.com/s/1wp6pTwsSR8QOZo0t3vNzYQ 密碼:2y2o