宏(#define)和類型別名(typedef)在結構和共用體(聯合體)類型定義中的應用

在學習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;
該例子定義了一個新類型,即消息,它是一種結構類型。該例子可以分爲兩部分來看,第一部分是先定義了一個結構類型tagMSG,第二部分是爲該類型取別名。先看第一部分:
struct tagMSG {
    HWND        hwnd;
    UINT        message;
    WPARAM      wParam;
    LPARAM      lParam;
    DWORD       time;
    POINT       pt;
#ifdef _MAC
    DWORD       lPrivate;
#endif
}
當_MAC已經定義時,該結構有七個成員,否則只有六個成員。
第一個成員,即在例一中提到的句柄,指向某一窗口,作爲消息傳遞的對象。
第二個成員,是消息編碼。每一個操作,比如鼠標左鍵,按下鍵盤等都對應一個編碼。從標識符UNIT的定義中可以看出它是無符號的整型。
第三個成員,是附加消息。從標識符WPARAM的定義看它也是無符號整型。
第四個成員,是附加消息。從標識符LPARAM的定義看它是長整型。
第五個成員,是消息投遞到消息隊列的時間。從DWORD的定義看,它是無符號長整型。字面意思是double word,表示雙字,每個字是2個字節,所以一共4個字節,與長整型一致。
第六個成員,是鼠標當前位置。標識符POINT的定義爲
typedef struct tagPOINT
{
    LONG  x;
    LONG  y;
} POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT;
它是一個結構體,用長整形定義了橫縱兩座標。該鼠標位置類型的定義和消息類型(當前例子)定義方式相同。
第七個成員,是私有消息(?) 同樣是無符號長整型。
總之,這七個成員組合成了一個比較詳細的消息結構體。
第二部分取別名:
typedef struct tagMSG MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
類似聲明變量,在一個基本類型下,可以聲明多個變量,包括指針變量,這裏給tagMSG結構類型取了一個別名叫做MSG,併爲它的指針取了一別名叫做PMSG。而NEAR和FAR其實是空宏,無任何意義(據說它們是之前16位機留下的痕跡,16位機內存尋址範圍只有64K,超出就要跨段,稱爲遠調用),所以後兩個都是結構指針別名。
 

例三、定義大整數類型

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)


參考資料:
[1], C++ Primer.
[2], 廣大網友
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章