今天在網上突然發現了下面幾個關于c代碼中的宏定義的說明,回想下,好像在系統的代碼中也見過這些零散的定義,但沒有注意,看到別人總結了下,發現果然很有用,雖然不知有的道可用與否,但也不失為一種手段,所以就先把它摘抄下來,增加一點見識:
1,防止一個頭文件被重復包含
#ifndef BODYDEF_H
#define BODYDEF_H
//頭文件內容
#endif
2,得到指定地址上的一個字節或字
#define MEM_B( x ) ( *( (byte *) (x) ) )
#define MEM_W( x ) ( *( (word *) (x) ) )
3,得到一個field在結構體(struct)中的偏移量
#define FPOS( type, field ) ( (dword) &(( type *) 0)-> field )
4,得到一個結構體中field所占用的字節數
#define FSIZ( type, field ) sizeof( ((type *) 0)->field )
5,得到一個變量的地址(word寬度)
#define B_PTR( var ) ( (byte *) (void *) &(var) )
#define W_PTR( var ) ( (word *) (void *) &(var) )
6,將一個字母轉換為大寫
#define UPCASE( c ) ( ((c) >= ''a'' && (c) <= ''z'') ? ((c) - 0x20) : (c) )
7,判斷字符是不是10進值的數字
#define DECCHK( c ) ((c) >= ''0'' && (c) <= ''9'')
8,判斷字符是不是16進值的數字
#define HEXCHK( c ) ( ((c) >= ''0'' && (c) <= ''9'') ||((c) >= ''A'' && (c) <= ''F'') ||((c) >= ''a'' && (c) <= ''f'') )
9,防止溢出的一個方法
#define INC_SAT( val ) (val = ((val)+1 > (val)) ? (val)+1 : (val))
10,返回數組元素的個數
#define ARR_SIZE( a ) ( sizeof( (a) ) / sizeof( (a[0]) ) )
11,使用一些宏跟蹤調試
ANSI標準說明了五個預定義的宏名。它們是:
_LINE_ (兩個下劃線),對應%d
_FILE_ 對應%s
_DATE_ 對應%s
_TIME_ 對應%s
_STDC_
宏中"#"和"##"的用法
我們使用#把宏參數變為一個字符串,用##把兩個宏參數貼合在一起.
#define STR(s) #s
#define CONS(a,b) int(a##e##b)
Printf(STR(vck)); // 輸出字符串"vck"
printf("%d\n", CONS(2,3)); // 2e3 輸出:2000
當宏參數是另一個宏的時候
需要注意的是凡宏定義里有用"#"或"##"的地方宏參數是不會再展開.
#define A (2)
#define STR(s) #s
#define CONS(a,b) int(a##e##b)
printf("%s\n", CONS(A, A)); // compile error
這一行則是:
printf("%s\n", int(AeA));
INT_MAX和A都不會再被展開, 然而解決這個問題的方法很簡單. 加多一層中間轉換宏.
加這層宏的用意是把所有宏的參數在這層里全部展開, 那么在轉換宏里的那一個宏(_STR)就能得到正確的宏參數
#define STR(s) _STR(s) // 轉換宏
#define CONS(a,b) _CONS(a,b) // 轉換宏
printf("int max: %s\n", STR(INT_MAX)); // INT_MAX,int型的最大值,為一個變量 #include<climits>
輸出為: int max: 0x7fffffff
STR(INT_MAX) --> _STR(0x7fffffff) 然后再轉換成字符串;
printf("%d\n", CONS(A, A));
輸出為:200
CONS(A, A) --> _CONS((2), (2)) --> int((2)e(2))
"#"和"##"的一些應用特例
1、合并匿名變量名
#define ___ANONYMOUS1(type, var, line) type var##line
#define __ANONYMOUS0(type, line) ___ANONYMOUS1(type, _anonymous, line)
#define ANONYMOUS(type) __ANONYMOUS0(type, __LINE__)
例:ANONYMOUS(static int); 即: static int _anonymous70; 70表示該行行號;
第一層:ANONYMOUS(static int); --> __ANONYMOUS0(static int, __LINE__);
第二層: --> ___ANONYMOUS1(static int, _anonymous, 70);
第三層: --> static int _anonymous70;
即每次只能解開當前層的宏,所以__LINE__在第二層才能被解開;
2、填充結構
#define FILL(a) {a, #a}
enum IDD{OPEN, CLOSE};
typedef struct MSG{
IDD id;
const char * msg;
}MSG;
MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
相當于:
MSG _msg[] = {{OPEN, "OPEN"},
{CLOSE, "CLOSE"}};
3、記錄文件名
#define _GET_FILE_NAME(f) #f
#define GET_FILE_NAME(f) _GET_FILE_NAME(f)
static char FILE_NAME[] = GET_FILE_NAME(__FILE__);
4、得到一個數值類型所對應的字符串緩沖大小
#define _TYPE_BUF_SIZE(type) sizeof #type
#define TYPE_BUF_SIZE(type) _TYPE_BUF_SIZE(type)
char buf[TYPE_BUF_SIZE(INT_MAX)];
--> char buf[_TYPE_BUF_SIZE(0x7fffffff)];
--> char buf[sizeof "0x7fffffff"];
這里相當于:
char buf[11];
C++提供的編譯預處理功能主要有以下三種:
(一) 宏定義
(二) 文件包含
(三) 條件編譯
在C++中,我們一般用const定義符號常量。很顯然,用const定義常量比用define定義常量更好。
在使用宏定義時應注意的是:
(a) 在書寫#define 命令時,注意<宏名>和<字符串>之間用空格分開,而不是用等號連接。
(b) 使用#define定義的標識符不是變量,它只用作宏替換,因此不占有內存。
(c) 習慣上用大寫字母表示<宏名>,這只是一種習慣的約定,其目的是為了與變量名區分,因為變量名
通常用小寫字母。
如果某一個標識符被定義為宏名后,在取消該宏定義之前,不允許重新對它進行宏定義。取消宏定義使用如下命令:
#undef<標識符>
其中,undef是關鍵字。該命令的功能是取消對<標識符>已有的宏定義。被取消了宏定義的標識符,可以對它重新進行定義。
宏定義可以嵌套,已被定義的標識符可以用來定義新的標識符。例如:
#define PI 3.14159265
#define R 10
#define AREA (PI*R*R)
單的宏定義將一個標識符定義為一個字符串,源程序中的該標識符均以指定的字符串來代替。前面已經說過,預處理命令不同于一般C++語句。因此預處理命令后通常不加分號。這并不是說所有的預處理命令后都不能有分號出現。由于宏定義只是用宏名對一個字符串進行簡單的替換,因此如果在宏定義命令后加了分號,將會連同分號一起進行置換。
帶參數的宏定義
帶參數的宏定義的一般形式如下:
#define <宏名>(<參數表>) <宏體>
其中, <宏名>是一個標識符,<參數表>中的參數可以是一個,也可以是多個,視具體情況而定,當有多個參數的時候,每個參數之間用逗號分隔。<宏體>是被替換用的字符串,宏體中的字符串是由參數表中的各個參數組成的表達式。例如:
#define SUB(a,b) a-b
如果在程序中出現如下語句:
result=SUB(2, 3)
則被替換為:
result=2-3;
如果程序中出現如下語句:
result= SUB(x+1, y+2);
則被替換為:
result=x+1-y+2;
在這樣的宏替換過程中,其實只是將參數表中的參數代入到宏體的表達式中去,上述例子中,即是將表達式中的a和b分別用2和3代入。
我們可以發現:帶參的宏定義與函數類似。如果我們把宏定義時出現的參數視為形參,而在程序中引用宏定義時出現的參數視為實參。那么上例中的a和b就是形參,而2和3以及x+1和y+2都為實參。在宏替換時,就是用實參來替換<宏體>中的形參。
在使用帶參數的宏定義時需要注意的是:
(1)帶參數的宏定義的<宏體>應寫在一行上,如果需要寫在多行上時,在每行結束時,使用續行符 "\"結
束,并在該符號后按下回車鍵,最后一行除外。
(2)在書寫帶參數的宏定義時,<宏名>與左括號之間不能出現空格,否則空格右邊的部分都作為宏體。
例如:
#define ADD (x,y) x+y
將會把"(x,y)x+y"的一個整體作為被定義的字符串。
(3)定義帶參數的宏時,宏體中與參數名相同的字符串適當地加上圓括號是十分重要的,這樣能夠避免
可能產生的錯誤。例如,對于宏定義:
#define SQ(x) x*x
當程序中出現下列語句:
m=SQ(a+b);
替換結果為:
m=a+b*a+b;
這可能不是我們期望的結果,如果需要下面的替換結果:
m=(a+b)*(a+b);
應將宏定義修改為:
#define SQ(x) (x)*(x)
對于帶參的宏定義展開置換的方法是:在程序中如果有帶實參的宏(如"SUB(2,3)"),則按"#define"命令行中指定的字符串從左到右進行置換。如果串中包含宏中的形參(如a、b),則將程序語句中相應的實參(可以是常量、變量或者表達式)代替形參,如果宏定義中的字符串中的字符不是參數字符(如a-b中的-號),則保留。這樣就形成了置換的字符串。 C++提供的編譯預處理功能主要有以下三種:
(一) 宏定義
(二) 文件包含
(三) 條件編譯
在C++中,我們一般用const定義符號常量。很顯然,用const定義常量比用define定義常量更好。
在使用宏定義時應注意的是:
(a) 在書寫#define 命令時,注意<宏名>和<字符串>之間用空格分開,而不是用等號連接。
(b) 使用#define定義的標識符不是變量,它只用作宏替換,因此不占有內存。
(c) 習慣上用大寫字母表示<宏名>,這只是一種習慣的約定,其目的是為了與變量名區分,因為變量名
通常用小寫字母。
如果某一個標識符被定義為宏名后,在取消該宏定義之前,不允許重新對它進行宏定義。取消宏定義使用如下命令:
#undef<標識符>
其中,undef是關鍵字。該命令的功能是取消對<標識符>已有的宏定義。被取消了宏定義的標識符,可以對它重新進行定義。
宏定義可以嵌套,已被定義的標識符可以用來定義新的標識符。例如:
#define PI 3.14159265
#define R 10
#define AREA (PI*R*R)
單的宏定義將一個標識符定義為一個字符串,源程序中的該標識符均以指定的字符串來代替。前面已經說過,預處理命令不同于一般C++語句。因此預處理命令后通常不加分號。這并不是說所有的預處理命令后都不能有分號出現。由于宏定義只是用宏名對一個字符串進行簡單的替換,因此如果在宏定義命令后加了分號,將會連同分號一起進行置換。
帶參數的宏定義
帶參數的宏定義的一般形式如下:
#define <宏名>(<參數表>) <宏體>
其中, <宏名>是一個標識符,<參數表>中的參數可以是一個,也可以是多個,視具體情況而定,當有多個參數的時候,每個參數之間用逗號分隔。<宏體>是被替換用的字符串,宏體中的字符串是由參數表中的各個參數組成的表達式。例如:
#define SUB(a,b) a-b
如果在程序中出現如下語句:
result=SUB(2, 3)
則被替換為:
result=2-3;
如果程序中出現如下語句:
result= SUB(x+1, y+2);
則被替換為:
result=x+1-y+2;
在這樣的宏替換過程中,其實只是將參數表中的參數代入到宏體的表達式中去,上述例子中,即是將表達式中的a和b分別用2和3代入。
我們可以發現:帶參的宏定義與函數類似。如果我們把宏定義時出現的參數視為形參,而在程序中引用宏定義時出現的參數視為實參。那么上例中的a和b就是形參,而2和3以及x+1和y+2都為實參。在宏替換時,就是用實參來替換<宏體>中的形參。
在使用帶參數的宏定義時需要注意的是:
(1)帶參數的宏定義的<宏體>應寫在一行上,如果需要寫在多行上時,在每行結束時,使用續行符 "\"結
束,并在該符號后按下回車鍵,最后一行除外。
(2)在書寫帶參數的宏定義時,<宏名>與左括號之間不能出現空格,否則空格右邊的部分都作為宏體。
例如:
#define ADD (x,y) x+y
將會把"(x,y)x+y"的一個整體作為被定義的字符串。
(3)定義帶參數的宏時,宏體中與參數名相同的字符串適當地加上圓括號是十分重要的,這樣能夠避免
可能產生的錯誤。例如,對于宏定義:
#define SQ(x) x*x
當程序中出現下列語句:
m=SQ(a+b);
替換結果為:
m=a+b*a+b;
這可能不是我們期望的結果,如果需要下面的替換結果:
m=(a+b)*(a+b);
應將宏定義修改為:
#define SQ(x) (x)*(x)
對于帶參的宏定義展開置換的方法是:在程序中如果有帶實參的宏(如"SUB(2,3)"),則按"#define"命令行中指定的字符串從左到右進行置換。如果串中包含宏中的形參(如a、b),則將程序語句中相應的實參(可以是常量、變量或者表達式)代替形參,如果宏定義中的字符串中的字符不是參數字符(如a-b中的-號),則保留。這樣就形成了置換的字符串。
解剖MFC自動生成的宏定義
一、關于DECLARE_MESSAGE_MAP宏定義
使用MFC向導,在ApplicationType頁面選擇DialogBased,生成一個對話框項目,Dialog類命名為CCapturePacketDlg,在CCapturePacketDlg.cpp中自動產生下列代碼:

2

3

- 先來分析ON_WM_PAINT(),在頭文件“afxmsg.h”有它的宏定義,如下:

2



3

4

2.1 #define WM_PAINT 0x000F
2.2 AfxSig_vv = AfxSig_v_v_v
2.2.1 enum AfxSig::AfxSig_v_v_v = 19
3.1 AFX_PMSG:typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void); //為一個函數指針
3.2 AFX_PMSGW:typedef void (AFX_MSG_CALL CWnd::*AFX_PMSGW)(void); //為一個函數指針
將ON_WM_PAINT()完全展開:



2

3

4

5

6

7

8

9

2. 再來分析BEGIN_MESSAGE_MAP(CCapturePacketDlg, CDialog),在“afxwin.h”中有定義:

2

3

4



5

6



7

8

9

10



2.1 PTM_WARNING_DISABLE:
#define PTM_WARNING_DISABLE \
__pragma(warning( push )) \ //#pragma warning( push [ ,n ] ),Where n represents a warning level (1 through 4).
//The pragma warning( push ) stores the current warning state for all warnings.
__pragma(warning( disable : 4867 ))//Do not issue the specified warning message(s).
//http://msdn2.microsoft.com/en-us/2c8f766e.aspx
// Allows selective modification of the behavior of compiler warning messages.
3.1 struct AFX_MSGMAP
{
3.1.1 const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
3.1.2 const AFX_MSGMAP_ENTRY* lpEntries;
};
3.1.2 struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT_PTR nSig; // signature type (action) or pointer to message #
3.1.2.1 AFX_PMSG pfn; // routine to call (or special value)
};
3.1.2.1 typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);
5.1 #define PASCAL __stdcall
將BEGIN_MESSAGE_MAP(CCapturePacketDlg, CDialog)完全展開:

2

3



4

5

6

7



8

9

10

11



3 最后分析END_MESSAGE_MAP(),在“afxwin.h”中有定義:

2



3

4

5



6

7

8

2.1 AfxSig_end:enum AfxSig.AfxSig_end = 0
2.2 AFX_PMSG:typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);//函數指針
4.1 struct AFX_MSGMAP
{
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
const AFX_MSGMAP_ENTRY* lpEntries;
};
8.1 #define PTM_WARNING_RESTORE \
__pragma(warning( pop ))
//pop restores the state of all warnings (including 4705, 4706, and 4707) to what it was at the beginning of the code.
·最后將

2

3


2

3



4

5

6

7



8

9

10

11



12



13

14

15

16

17

18

19

20

21

22



23

24

25

26

27

28

29

30

31

32



33

34

35

36

37

38

39

老辦法查看它的定義:

2

3

4

小結:
每次用MFC類向導生成一個類時,系統會在類的聲明部分添加兩個方法的聲明:GetThisMessageMap(),GetMessageMap()。在類的實現部分.cpp文件中加上這兩個方法的定義。
當然這所有的代碼都是由系統生成的,如果我們要定義自己的消息處理函數呢,例如,我們要添加一個按鈕(ID為:IDC_BUTTON1)的單擊處理函數我們可以添加宏ON_NOTIFY(NM_CLICK, IDC_BUTTON1, OnMyClick),OnMyClick為自定義函數,但是他必須與ON_NOTIFY中的函數原型一致。
二、關于DECLARE_DYNCREATE宏
使用MFC向導,在ApplicationType頁面選擇SingleDocument,生成一個單文檔項目,Document類命名為CDynamicDoc,在CDynamicDoc.h中自動產生DECLARE_DYNCREATE(CDynamicDoc),CDynamicDoc.cpp中產生IMPLEMENT_DYNCREATE(CDynamicDoc, CDocument)。
1、展開CDynamicDoc.h中的DECLARE_DYNCREATE(CDynamicDoc):

2

3

4


2

3

4

5

6

7

8


2

3

4

5

6

7

2、展開CDynamicDoc.cpp中的IMPLEMENT_DYNCREATE(CDynamicDoc, CDocument):

2

3



4

5


2

3



4



5

6

7

8



9

10




2



3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31



2

3

4

5

6

7




8

9

10



11

12

13

14

15

16

17

18



19

20

21

4.1.3 RUNTIME_CLASS如下定義:


“##”——operator (##), which is sometimes called the "merging" operator, is used in both object-like and function-like macros.
4.1.8 _RUNTIME_CLASS如下定義:










































如果你想看這些宏的簡化版,可以參考侯老的《深入淺出MFC》,如下:
1//in header file
2class CView : public CWnd
3{
4public:
5 static CRuntimeClass classCView;
6 virtual CRuntimeClass* GetRuntimeClass() const;
7 //……
8};
9//in implementation file
10static char_lpszCView = "CView";
11CRuntimeClass CView::classCView =
12{
13 _lpszCView
14 , sizeof(CView)
15 , 0xFFF
16 , NULL
17 , &CWnd::classCWnd
18 , NULL
19};
20static AFX_CLASSINIT _init_CView(&CView::classCView)
21{
22 (&CView::classCView)->m_pNextClass = CRuntimeClass::pFirstClass;
23 CRuntimeClass::pFirstClass = &CView::classCView;
24}
25CRuntimeClass* CView::GetRuntimeClass() const
26{
27 return &CView::classCView;
28}
其中他將CRuntimeClass簡化定義為:
struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema; // schema number of the loaded class
CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
CRuntimeClass* m_pBaseClass;
// CRuntimeClass objects linked together in simple list
static CRuntimeClass* pFirstClass; // start of class list
CRuntimeClass* m_pNextClass; // linked list of registered classes
};

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20



21

22

23

24

25

26



27

28

29

30

31

32




33

34

35



36

37

38

39

40

41

42

43

44

45



46



47

48

49

50



51

52



53

54

55

56


2

3

4

5

6

7

8

9

10

11

12

13



14

15

16

17

18



19

20

21

22

23



24

25

26

27

28



29

30

31

32

33

34

35

36

37

38



39

40

41

42

43



44

45



46

47

48

49

50

51

52

53



54

55

56

CStroke::classCStroke =
{
"CStroke"
, sizeof(class CStroke)
, 1
, CStroke::CreateObject
, &class_name::_GetBaseClass
, NULL
, &_init_CStroke
}
其中,由extern AFX_CLASSINIT _initCStroke可知_init_CStroke是一個結構體AFX_CLASSINIT的對象,此結構體有構造函數:

2

3




4

5

6



7

8

9

10

11
