在将源代码提交给编译器之前,C语言预处理器将对源代码做出一定修正。预处理器命令有很多如最常用的#include#define命令.

预处理命令都是以#开头,一般放在代码的最左侧,通常定义的宏全部都是用大写。

条件编译

下面这一系列的指令都可以用来决定代码的哪个部分应该被编译,这些指令包括:#if,#elif,#else,#ifdef,#else,#ifdef以及#ifndef。以#if开头的语句块必须以#endif结尾。

可以使用条件编译来注释代码,例如


  1. #if 0 
  2.  
  3. /* comment ... 
  4.  
  5. */ 
  6.  
  7.   
  8.  
  9. // code 
  10.  
  11.   
  12.  
  13. /* comment */ 
  14.  
  15. #endif 

#if 表达式,如果表达式为假,那么这部分代码就不会被编译,为真才参与编译。虽然你会说使用块注释也是可行的,但是可惜的是块注视不支持嵌套,在某些环境下选择#if 0确实是一个更好的选择。测试代码:

 

  1. #include<stdio.h> 
  2.  
  3. int main() 
  4.  
  5.  
  6. #if 0 
  7.  
  8.       int a=10; 
  9.  
  10. #endif 
  11.  
  12.   
  13.  
  14. #if 1 
  15.  
  16.       int a=11; 
  17.  
  18. #endif 
  19.  
  20.       printf("a=%d\n",a); 
  21.  

可以好似用#ifndef来避免头文件被重复编译:例如下面这段代码:


  1. #ifndef _FILE_NAME_H_ 
  2. #define _FILE_NAME_H_ 
  3.   
  4. /* code */ 
  5.   
  6. #endif // #ifndef _FILE_NAME_H_ 

同样的技术可以用来定义一些常量,如:


  1. #ifndef NULL 
  2. #define NULL (void *)0 
  3. #endif // #ifndef NULL 

 

宏定义

使用简单的宏就不多说了,对于比较复杂的宏,常见的是使用类似于函数定义的宏,使用这种宏要特别注意宏的副作用。例如下面这段代码:

#define MULT(x, y) x * y
int z = MULT(3 + 2, 4 + 2);

一般情况,要对所有的实参添加括号,同时对宏体整体添加括号。

下面考察一种更加复杂的情况,交换两个数的宏定义可以写为:


  1. #define SWAP(a, b)  a ^= b; b ^= a; a ^= b;  
  2. int x = 10; 
  3. int y = 5; 
  4.   
  5. // works OK 
  6. SWAP(x, y); 
  7.   
  8. // What happens now? 
  9. if(x < 0) 
  10.     SWAP(x, y); 
 

对于第if语句会出现逻辑错误,因为这个if语句只执行第一个异或操作。嗯,或许你已经想到了,加上大括号,像下面这样:


  1. #define SWAP(a, b)  {a ^= b; b ^= a; a ^= b;}  

然而,还是对于第二个if语句还是不正确,因为预处理器替换之后就出现了‘};’语法错误。这里有一个技巧,正确的写法如下:


  1. #define SWAP(a, b)  do { a ^= b; b ^= a; a ^= b; } while ( 0 ) 

这下就可以了,这里没有给实参加括号的原因是因为我们只是针对变量的交换,不考虑那些表达式交换。

 

使用\分割宏体

 

如果宏体比较复杂的话,我们就需要使用\来分割宏体,示例代码如下所示:


  1. #define SWAP(a, b)  {                   \ 
  2.                         a ^= b;         \ 
  3.                         b ^= a;         \  
  4.                         a ^= b;         \ 
  5.                     }  

 

表达式合并

有时候在宏体中我们需要连接两个参数的名字这时候可以使用##


  1. struct command 
  2. char *name; 
  3. void (*function) (); 
  4. }; 
  5. struct command commands[] = 
  6. "quit", quit_command}, 
  7. "help", help_command}, 
  8. {"run",run_help}, 
  9. {"open",open_help} 
  10. }; 

struct中显然每个名字都重复了一遍,通过使用##可以减少重复,方便维护:


  1. #define COMMAND(NAME) { #NAME, NAME ## _command } 
  2. struct command commands[] = 
  3. COMMAND (quit), 
  4. COMMAND (help), 
  5. COMMAND (run), 
  6. COMMAND (open) 
  7. }; 

符号字符串化

有时候我们经常需要将传入到宏中的符号或者叫做参数转换为文本形式,这样对于打印这些符号的时候比较方便,在写一些调试模块的时候,经常会遇到这样的需求,实现这个过程的语法很简单,只需要在符号前面添加一个‘#’就可以了。例如下面这段代码:


  1. #define PRINT_TOKEN(token) printf(#token " is %d", token) 
  2. PRINT_TOKEN(foo); 

PRINT_TOKEN(foo);将被扩展 printf("<foo>" " is %d" <foo>)

新版本的C语言支持将多个使用双引号括起来的字符串练成一个整体的字符串。

下面为测试代码:

 

  1. #include<stdio.h> 
  2.  
  3. #define PRINT_TOKEN(token) printf(#token " is %d\n",token); 
  4.  
  5. int main() 
  6.  
  7.  
  8.       int x=10,y=20; 
  9.  
  10.       PRINT_TOKEN(x+y); 
  11.