1. 程式人生 > >巨集(#define)和類型別名(typedef)在結構和共用體(聯合體)型別定義中的應用

巨集(#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], 廣大網友

相關推薦

巨集#define別名typedef結構共用聯合體型別定義應用

在學習cocos2d-x的過程中,經常看到各種大寫的識別符號,有些是自定義的巨集,有些是複雜型別的別名。前者用#define來實現,後者用typedef來實現。它們的存在有兩個共同目的(當然還有其他不同的目的),一是用簡單的識別符號來代替複雜的程式碼,二是(在條件編譯語句的

C++快速入門---聯合、列舉別名7

C++快速入門---聯合、列舉和類型別名(7)   聯合可以容納多種不同型別的值,但是它每次只能儲存這些值中的某一個。 #include <iostream> union mima { unsigned long birthday; unsigned shor

深入淺出TypeScript4- 使用介面別名

在TypeScript中,為了可以約束物件定義,提供了兩個新的特性,介面和類型別名。 TypeScript中的介面 在強型別語言中,都有介面的概念,那麼TypeScript中的介面是如何使用的呢? 介面定義形式如下: interface test { name: string, valu

C++別名typedefusing

前言 隨著程式越來越複雜,程式中用到的型別也越來越複雜,這種複雜性有兩方面: (1)一些型別難以“拼寫”,它們的名字既難記住用容易出錯,還無法明確體現其真實目的和含義; (2)有時候根本搞不清楚到底需要什麼型別,程式設計師不得不回頭從程式的上下文尋求幫助。 這裡主要針

C# 關鍵字explicit顯示,implicit隱式的隱式顯式轉換

tar oid bsp color col 必須 code 類型 顯示 class Program { static void Main(string[] args) { Adaptee ada = ne

值傳遞引用傳遞不是引用的傳遞的區別

com this static 實現 pre 對象 ffffff -c wap 值傳遞:方法調用時,實際參數把它的值傳遞給對應的形式參數,方法執行中形式參數值的改變不影響實際參數的值。引用傳遞:也稱為傳地址。方法調用時,實際參數的引用(地址,而不是參數的值)被傳遞給方法中

JavaScript的進階之路引用之ObjectArray

reverse 代碼 -1 替換 fine 設置 sha unshift sum 引用類型 Object類型 function a(num){ if(num>3){ a(--num); } console.log(num);

Java學習筆記二十一轉換instanceof關鍵字

方法 png true feed out 實例 strong 運算符 nbsp 基本數據類型轉換: 自動類型轉換:把大類型的數據賦值給大類型的變量(此時的大小指的是容量的範圍) 1 byte b = 12; //byte是一個字節 2 int i = b; //i

python學習筆記數值轉換

學習 系統 oat cal 關於 trac hide sed lin Python中的數值類型有:   整型,如2,520   浮點型,如3.14159,1.5e10   布爾類型 True和False e記法:   e記法即對應數學中的科學記數法 1 >>

抽象數據ADT面向對象編程OOP3.1數據檢查

字符串 9.png lac per 不能被繼承 不變 play 困難 及其 數據類型在編程語言中: 類型是一組值以及可以對這些值進行操作 變量 存儲一個特定類型值的命名位置 基本數據類型: int 限制在±2 ^ 31的範圍內,或者大約為±20億

C# List 賦值 --引用的賦值復制

地址 修改 引用 進行 urn ont pre 詳細 理論 最近項目維護中遇到一個問題,確切的說應該是兩個月前的問題也是因為這裏引起的,可惜當時困於業務不熟悉,也沒有更多時間允許查詢根源,導致再次引發了新的問題!!! 問題場景:基礎數據存於List類型的BOMs中,計算過程

Python面向物件程式設計例項 訪問限制 繼承 獲取物件資訊 例項屬性屬性

面向物件程式設計——Object Oriented Programming,簡稱OOP,是一種程式設計思想。OOP把物件作為程式的基本單元,一個物件包含了資料和操作資料的函式。 資料封裝、繼承和多型是面向物件的三大特點 在Python中,所有資料型別都可以視

oracle的concat、convert、listagg函數字符串拼接轉換

dual name ase oracl wid 返回值 說明 laserjet 基於 ORACLE幾種常用的方法(2) 1、concat常見的用法 :   格式:concat(String1,String2)   說明:concat函數用於將兩個字符串連接起來,形成一

C#學習筆記二 資源托管,泛,數組元組,運算符強制轉換

方法 約束 資源 實例 對象 where 學習筆記 如果 數據庫連接 1.托管資源是指GC管理的內存空間,非托管資源是指文件句柄,網絡連接,數據庫連接等。 2.方法中臨時申請的變量,被存放在棧中。棧存儲非對象成員的值數據。例如在方法中有B b=new B(); b的

redis數據四之hash的指令操作五種數據最重要的一種

redis數據類型 hash的指令操作 1、老規矩,看看redis官方文檔怎麽寫: 這裏說呢,hashes 這種數據類型容易代表對象,實際上你可以無限制地在 hash 中放給定數量的字段。(除了可用的內存)(其實這個我理解有些模糊,是內存能夠滿足就可以無限制的放入嗎?)所

快學Scala 第一課 變量,,操作符

引用類型 lan 轉換成 double 算數 字符串 字符串追加 img 方法 Scala 用val定義常量,用var定義變量。 常量重新賦值就會報錯。 變量沒有問題。 註意:我們不需要給出值或者變量的類型,scala初始化表達式會自己推斷出來。當然我們也可以指

JDBC入門5--- 時間、大數據

服務器 setting fun exceptio finall trace rep rest bytes 一、時間類型 數據庫類型與Java中類型的對應關系: DATE->java.sql.Date:表示日期,只有年月日,沒有時分秒,會丟失時間。 TIME->j

ASP.NET新聞系統添加新聞

mage .exe grid script .com cmd values 鏈接 代碼 最近剛開始學習webfrom。先做一個新聞系統,練練手吧 textbox控件和button按鈕控件 接收textbox內容,點擊添加按鈕,添加到數據庫。 建立sql server數據庫

第19課 萃取3_選擇的traits

可用 class pac 利用 tro 功能 *** typename res 1. std::conditional (1)原型:template <bool Cond, class T, class F> struct conditional; //根據條

Python基礎續基礎數據

類型 log 順序 小寫 次數 原則 大小寫 true 常用 一整體初識數據類型 1.1int 數字主要是用於計算用的,使用方法並不是很多,就記住一種就可以: #bit_length() 當十進制用二進制表示時,最少使用的位數 i = 4 print(i.bit_leng