C语言宏的使用

 

使用条件宏进行条件编译

譬如,对于同一份代码,我想编译出两个不同的版本,在其中一个版本中去掉某一部分功能,
这时可以通过条件宏判断是否编译,例:
C语言_宏1.png
如果不使用条件宏进行控制,想编译两个不同版本的程序,就需要保存两份源代码。

条件编译的语法和if else语法类似,必须以#endif结尾例如:

#if 常量表达式
 //代码1
#elif 常量表达式
 //代码2
#elif 常量表达式 
 //代码3
#else 
 //代码4
#endif

条件编译时将会根据常量表达式来求值,如果常量表达式的值为假,那么对应的代码将不会被 编译,还可以判断某个宏是否定义,例如:

#ifdef(NameOfMacro)
     //代码1
#else
     //代码2
#endif

如果NameOfMacro被定义,那么代码1将会参与编译,否则代码2参与编译,如下写法也是等价的:

#if defined(NameOfMacro)
     //代码1
#else
     //代码2
#endif
#if !defined(NameOfMacro)
     //代码2  
#else
     //代码1
#endif

宏一般都定义在头文件中,要想使用改宏则使用#include包含该头文件即可,也可以定义在源文件中,但是这种情况比较少,一定要确保条件判断中的宏的定义位置位于条件判断之前,否则不会生效;宏的作用范围从定义它的位置开始到源文件结束,在其他源文件中该宏不可见,如果想使用某个宏,则必须使用#include指令包含对应的头文件。
除了在头文件和源文件中定义宏,还可以在定义预处理宏,
例如:在VS项目属性中
C语言_宏2.png
预处理宏只能是空的宏,但预处理宏作用于整个工程,对该工程中任意源文件可见,优先于任何定义在源码中的宏.

使用空宏进行说明

可以使用空宏对函数参数进行说明,微软的API中就使用了空宏,例如:

   WINBASEAPI
   BOOL
   WINAPI
   WriteFileEx(
             _In_ HANDLE hFile,
             _In_reads_bytes_opt_(nNumberOfBytesToWrite) LPCVOID lpBuffer,
             _In_ DWORD nNumberOfBytesToWrite,
             _Inout_ LPOVERLAPPED lpOverlapped,
             _In_ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
             );

In,_Inout_定义如下:
C语言_宏3.png
_In_表示该参数为传入参数,Inout_表改参数为传入传出参数,为了防止_In,_Inout_被
别人定了,所以先使用#ifdef判断该宏是否被定义,如果被定义则使用#undef取消定义,然后
在重新定义该宏。

#和##运算符

除了#define 标识符 记号序列 这种形式定义宏,还可以定义带有参数的宏,形式如下:
#define 标识符(标识符列表(可选)) 记号序列,
在定义带有形参的宏定义中,#和##将会影响宏替换的过程,如果记号序列中某一个参数前
面有一个#,那么替换时将会在这个参数前后插入",将其变成一个字符串,即使该参数是
另一个带参宏,也不会在进行宏展开替换;如果该参数为一个字符串常量,那么预处理器会
分别为字符串前后的"号进行转义,将"变成字符,然后在前后各插入一个";

例:

 #define TEST0(p1) #p1"333"
 #define TEST(p1,p2,p3) #p1#p2 #p3
 int main()
 {
   int nTest1;
   int nTest2;
   int nTest3;
   printf("%s", TEST(TEST0(0),"333",nTest3));
   return 0;
 }

运行结果:
带参宏定义.png

从上面的例子可以看出#运算符主要作用就是将宏定义中的参数变成字符串,而##运算符的
作用就是将宏定义中相邻的两个的参数连接在一起组成一个标识符,如果该标识符不符合C
语言表示符的组成规则,那么编译时会报错;
例如:
带参宏定义1.png

对于使用了##和#的宏定义,预处理器需要进行反复扫描分析记号序列,以此来查找已定义的
标识符进行替换,如果一个表示符在某次扫描中被替换后,再次扫描遇到此标识符时,则不在
进行替换。
例:

 #define TEST(p1,p2) p1##p2
 int main()
 {
   int nTest1;
   int nTest2;
   int nTest3;
   int TEST(TEST(nTest1, nTest2), nTest3) = 3;
   printf("%d", TEST(TEST(nTest1, nTest2), nTest3));
   return 0;
 }

带参宏定义2.png

使用/P命令查看编译预处理后的结果:
大P命令.png

 int main()
 {
   int nTest1;
   int nTest2;
   int nTest3;
   int TEST(nTest1, nTest2)nTest3 = 3;
   printf("%d", TEST(nTest1, nTest2)nTest3);
   return 0;
 }

可以看出TEST(TEST(nTest1, nTest2), nTest3)第一次展开后为TEST(nTest1, nTest2)nTest3,
然后又遇到了TEST宏定义,则不在进行展开,直接将TEST(nTest1, nTest2)nTest3作为最终结果,但是
TEST(nTest1, nTest2)nTest3显然不符合C语言表示符的定义,所以编译时会报错。

注意:/P命令使用完成后,必须从命令行中删除它,否则编译的时候会报找不到xxxxx.obj错误

#和##运算符是C语言宏定义中两个比较有创造力的运算符,可以创造出许多"黑魔法"宏定义

预定义的宏

C语言中有些预定义的宏,这些宏不能取消定义和重定义:
C语言标准预定义的宏.png

 

posted @ 2019-06-28 11:04  CodeMaker+  阅读(1878)  评论(0编辑  收藏  举报