程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 探討C++中的Map映射機制

探討C++中的Map映射機制

編輯:C++入門知識

概述

從MFC到ATL,充斥著Map映射機制,似乎沒有了這個Map機制,就玩不轉啦。在WebBrower控件中,也存在著事件映射;在COM中,在IDispatch中也存在著自定義的函數映射。

以前,只要一談到映射機制,總是讓我聞風喪膽,退而求自保,暫且如此而已,記住就可以啦。現在想來,只要是跨不去過的坎,若沒有認真面對和解決,那就永遠無法逾越,成為心中永遠的痛。最終,只能作繭自縛而唯唯諾諾。既然老天爺,又給了我一次機會,那我就好好抓住這次機會啦。

轟轟烈烈的開場白講完了,讓我們回歸主題:“映射機制”

格式

Windows消息的Map格式

map代碼,如下所示:

	BEGIN_MSG_MAP(CTestDialog)
		MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
		MESSAGE_HANDLER(WM_CLOSE, OnClose)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
	END_MSG_MAP()
其實是三個define所構成的,如下所示:

#define BEGIN_MSG_MAP(theClass) \
public: \
	BOOL ProcessWindowMessage(_In_ HWND hWnd, _In_ UINT uMsg, _In_ WPARAM wParam,\
		_In_ LPARAM lParam, _Inout_ LRESULT& lResult, _In_ DWORD dwMsgMapID = 0) \
	{ \
		BOOL bHandled = TRUE; \
		(hWnd); \
		(uMsg); \
		(wParam); \
		(lParam); \
		(lResult); \
		(bHandled); \
		switch(dwMsgMapID) \
		{ \
		case 0:

#define MESSAGE_HANDLER(msg, func) \
	if(uMsg == msg) \
	{ \
		bHandled = TRUE; \
		lResult = func(uMsg, wParam, lParam, bHandled); \
		if(bHandled) \
			return TRUE; \
	}


#define END_MSG_MAP() \
			break; \
		default: \
			ATLASSERT(FALSE); \
			break; \
		} \
		return FALSE; \
	}




從中我們可以看到,頭文件的map宏,確實有3個define所構成,其本質是定義了一個ProcessWindowMessage函數。在
#define MESSAGE_HANDLER(msg, func) \
中,msg和func由用戶選擇,所以就暴露這2個參數。若有3個參數有用戶選擇的話,則肯定會暴露3個參數啦。

一般來說,最後一個參數是函數名稱或函數地址,而它之前的參數一般都是它的參數。這樣就解決了,只用一個宏,就可以解決所有相同個數和類型的輸入參數,但不同操作的一般化函數調用,即程序中的映射機制。

通常情況下,映射一詞有照射的含義,是一個動詞。在數學上,映射則是個術語,指兩個元素集之間元素相互“對應”的關系,名詞;也指“形成對應關系”這一個動作,動詞。

(摘自百度百科)

說白了,就是一種對應關系嘛,就這麼簡單,沒有啥可說的。

模板類的Map格式

在頭文件中,我們定義如下格式的映射關系:

BEGIN_XXX_MAP(CClassName)

XXX_ENTRY(String1, Identify1, OnFunctionName1)

XXX_ENTRY(String2, Identify2, OnFunctionName2)

... ...

XXX_ENTRY(StringN, IdentifyN, OnFunctionNameN)

END_XXX_MAP()

那麼,我們現在可以理解為OnFunctionName函數需要String和Identify這兩個變量。由於有若個這樣的XXX_ENTRY,那麼就會有相應個函數,暫且成為函數容器。

這時我們就會想到兩種情況來解釋個函數容器:

一種是:上面所說的“Windows消息映射”,它只是將消息和函數進行一一對應,則程序更富有表現力,同時隱藏了不必要的代碼。並且對應關系比較簡單,就是一個類函數指針的代理。

另一種是:若個函數作為函數容器出現,以便在對應的模板類中對容器中的各個函數進行輪詢,以便決定是否使用具有特定碼的函數。它不再作為一個代理的角色出現,而更多地是扮演成員變量數組的角色出現。

這樣做的好處是,讓模板類可以更加靈活的處理這個數組,以便完成特定的處理效果。解放了數據和函數,分別進行了處理。咱們職責分明,秋毫無犯嘛,呵呵。

注意事項:

(1)定義GetMap()函數

它一般被const staic所修飾,其返回值為指向模板數組的一個指針;這樣在函數中引用該GetMap時,只需要使用T::GetMap即可,因為它是靜態函數啊!如下為保存和顯示三個變量關系的結構體:

template
struct ST_XXX_ENTRY
{
	typedef void(T::*Function_Name)();
	LPSTR string;
	UINT identify;
	Function_Name func;

	static void ProcessFunc1()
	{
		//...
	}

	static void ProcessFunc2()
	{
		// ...
	}
};
結構體固然重要,但是這裡更為重要的是展現三個變量關系之間的靜態函數。


map的實例化代碼如下所示:

#define BEGIN_XXX_MAP(theClassName)\
	static const ST_XXX_ENTRY * GetMap() \
	{ \
		static ST_XXX_ENTRY theMap[] = \
		{

#define  XXX_ENTRY(string, identify, func) \
			{ string, identify, &theClassName::func},\ // 此處應用了類函數指針的獲取方法

#define  END_XXX_MAP() \
			{ NULL, 0, ST_XXX_ENTRY::Function_Name(NULL)} \
		} \
	}

(2)調用GetMap函數

在父類模板中,必然定義了如何使用GetMap中的函數映射關系。那麼此時的調用,必然是直接使用T::GetMap()來獲得靜態容器的指針,然後對它進行遍歷和篩選,以期獲得我們想要點對應函數或對應函數上的處理結果。


非常棒,到這裡,我們已經基本講完了如何關聯map和實例化map,以及變量在結構體中的定義。呵呵,感覺越寫越有感覺,越寫越明白裡面map機制的奧秘在哪裡已經如何外化出這個奧秘。

客戶(界面)代碼

客戶代碼是使用map宏的代碼,它會繼承一個模板類,而模板類所需要的實例類便是客戶類,為什麼會是這個樣子呢?

其實原因很簡單,因為此處模板類就是將公共函數提取出來,並且統一處理map宏中的轉換關系,從而精簡客戶代碼。而客戶類完全可以按照客戶所想定義的方式定義,想如何命名類名就如何命名類名,很自由。唯一要做的就是繼承一下模板類,並且添加自己喜歡的對應關系即可,想用什麼函數名就用什麼函數名,想用什麼id就用什麼id,因為map的實例化只是引用函數指針,跟名字一點關系都沒有。夠爽了吧,一個“牛爽”。

可話又說回來,所有的模板類不正是可以容納各中類而存在,並且統一化處理流程的嘛。原來,我們從實踐中,再次感受到模板的優點,或者說它的使命:

(1)模板更有助於編寫。我們只需創建類或函數的一個泛型版本,而不是手動創建專用化;

(2)模板是類型安全的。 由於模板操作的類型在編譯時是已知的,因此編譯器可以在發生錯誤之前執行類型檢查;

(3)由於可通過模板直接提取信息,因此模板更易於理解。(當然是這樣的,若僅僅查看模板的話,顯得比較抽象,若通過模板來實例化一個對象後,則提取信息變得可視化,確實易於理解。)

這三個優點,我是從msdn上摘的,不過稍微潤色了一下,使得主旨更加明晰(畢竟翻譯e文,仁者見仁哦)。


到此,我已經講完了映射機制,Windows的所有映射機制,大抵如此,照葫蘆畫瓢。


真沒有想到,居然寫了這麼多。不過真心體會,寫完這篇blog之後,感覺對映射機制如釋重負,感覺從未有過的輕松自在。越發覺得,寫blog是一個很不錯的深入學習的體驗。只有在寫得過程中,才會感受到那種順籐摸瓜的感覺。


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved