宏(#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], 广大网友

你可能感兴趣的:(cocos2d-x,C++)