在學習cocos2d-x的過程中,經常看到各種大寫的標識符,有些是自定義的宏,有些是複雜類型的別名。前者用#define來實現,後者用typedef來實現。它們的存在有兩個共同目的(當然還有其他不同的目的),一是用簡單的標識符來代替複雜的代碼,二是(在條件編譯語句的幫助下)實現平臺無關代碼。這是cocos2d-x大量使用大寫標識符的原因。從#define前面的井號可以看出,它屬於預編譯指令,而typedef不是。下面舉三個例子來說明他們的妙用。
例一、定義句柄類型
winnt.h
#ifdef STRICT
typedef void *HANDLE;
#if 0 && (_MSC_VER > 1000)
#define DECLARE_HANDLE(name) struct name##__; typedef struct name##__ *name
#else
#define DECLARE_HANDLE(name) struct name##__{int unused;}; typedef struct name##__ *name
#endif
#else
typedef PVOID HANDLE;
#define DECLARE_HANDLE(name) typedef HANDLE name
#endif
typedef HANDLE *PHANDLE;
該例子定義了一個windows程序設計中的常用類型,即句柄,用於標識窗口。其中有多個條件編譯指令。#if(#ifdef)和#endif形成一個語塊,從外向裏包圍。爲了看得更加清楚,我們故意將代碼縮進顯示。該例子中既有宏也有類型別名,兩者配合使用,奇妙無窮。下面我們就來具體分析一下。
1)假如STRICT已有定義,則爲任意類型指針取一別名,叫做HANDLE(句柄)。並且繼續判斷,如果 0&& (_MSC_VER>1000)爲真(顯然不可能,但是可以人工修改令其爲真),則給出宏DECLARE_HANDLE(name)(望文生義可以看出是用來聲明句柄的)的第一種定義,否則給出宏的第二種定義。
在第一種定義中宏的真身是struct name##__; typedef struct name##__ *name(注意這裏句末沒有分號,這是因爲#define屬於預處理指令,在編譯過程中會將真身直接替代宏放在相應的代碼之中,而宏往往位於句首,而且宏所在代碼句末必定已有分號存在)。其中##是連字符(預處理運算符),表示將前後字符串(至少有一個是待定的,否則沒必要使用)拼接起來。而下劃線__並無意義,只是爲了防止重名作爲字符串接在待定字符串name後面。下面舉例說明。如DECLARE_HANDLE(HWND); 就表示struct HWND__; typedef struct HWND__ *HWND;(如果不加下劃線__,HWND就重名了)。這是什麼意思呢?先聲明瞭一個HWND__結構(但無定義),後爲該結構指針取了一別名叫HWND。所以當使用HWND hwnd;時就意味着hwnd是HWND__結構指針。再如DECLARE_HANDLE(HHOOK); HHOOK hhook; 就是聲明瞭一個HHOOK__結構指針hhook。值得注意的是,雖然hwnd和hhook都是指針,但是指向不同結構類型(一個是HWND__結構,一個是HHOOK__結構),所以無法相互轉換,非常巧妙的增強了代碼的安全性。所以使用宏DECLARE_HANDLE來聲明句柄,不僅看起來簡單,而且聲明的句柄實屬不同類型,互相間不能轉換。但是它們都屬於HANDLE句柄,因爲用HANDLE聲明的句柄指向任意類型,所以HANDLE句柄可以轉換成DECLARE_HANDLE句柄,層次分明。
在第二種定義中,宏的真身是struct name##__{int unused;}; typedef struct name##__ *name。與第一種區別僅在於多了一個結構體。該結構體裏含有一個整型變量。
2)假如STRICT沒有定義,則給PVOID取個別名叫HANDLE。PVOID的定義和前面的HANDLE一樣是個指向任意類型的指針。然後定義宏DECLARE_HANDLE(name)爲typedef HANDLE name。所以就是爲HANDLE又取了個別名。因此在這種情況下,HANDLE句柄和DECLARE_HNADLE句柄都是指向任意類型的指針。
3)爲指向HANDLE類型(指向任意類型的指針)的指針取一別名叫PHANDLE,用來聲明指針的指針。
例二、定義消息結構類型
WinUser.h
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
#ifdef _MAC
DWORD lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
#ifdef _MAC
DWORD lPrivate;
#endif
}
typedef struct tagPOINT
{
LONG x;
LONG y;
} POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT;
typedef struct tagMSG MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
例三、定義大整數類型
winnt.h
#if defined(MIDL_PASS)
typedef struct _LARGE_INTEGER {
#else // MIDL_PASS
typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
LONG HighPart;
} DUMMYSTRUCTNAME;
struct {
DWORD LowPart;
LONG HighPart;
} u;
#endif //MIDL_PASS
LONGLONG QuadPart;
} LARGE_INTEGER;
typedef LARGE_INTEGER *PLARGE_INTEGER;
該例子定義了一個大整數類型。條件編譯語句將該定義分成了兩種情況,當MIDL_PASS已被定義時,取第一種情況,否則取第二種情況。第一種情況下,定義爲
typedef struct _LARGE_INTEGER {
LONGLONG QuadPart;
} LARGE_INTEGER;
根據例二的經驗,可知該大整數是一結構類型的別名,其中只有一個成員,爲超長整型變量。在第二種情況下,大整數定義爲
typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
LONG HighPart;
} DUMMYSTRUCTNAME;
struct {
DWORD LowPart;
LONG HighPart;
} u;
LONGLONG QuadPart;
} LARGE_INTEGER;
該大整數是一共用體類型的別名,它有三個成員,前兩個是結構變量(一個爲DUNMMYSTRUCTNAME,另一個爲u),後一個是超長整型變量(注意這裏講的是變量,而非類型)。前兩個的結構變量的類型一致,內有兩個成員,一是無符號長整型變量,二是長整型變量。
共用體和結構的區別在於,共用體的成員共享一個內存段,因此當給其中一個成員賦值,原有成員便被覆蓋。從中我們可以看出用該大整數類型聲明的變量每一時刻也只有一種取值,而非三種取值。
該例子最後一行代碼給大整數類型的指針取了一別名,叫做PLARGE_INTEGER。
以上例子都可以從coco2d-x中進入,然後通過轉到定義來找到:
例一:run()(CCApplication.h/cpp), MSG(WinUser.h), HWND(windef.h),DECLARE_HANDLE(winnt.h)
例二:run()(CCApplication.h/cpp), MSG(WinUser.h)
例三:run()(CCApplication.h/cpp), LARGE_INTEGER(winnt.h)