C语言源程序的加工分为三个步骤:预处理,编译和链接。任何C系统里都有一个预处理程序,它是系统的一个组成部分,负责处理源程序里所有的预处理命令,生成不含预处理命令的源程序。C语言提供预处理命令的目的是为了使编程更方便。
如果源程序里某行的第一个非空符号是#,则该行就是预处理命令。下面是各种预处理命令:
1. 文件包含命令:#include
2. 宏定义与宏替换:由#define开始的行称为宏定义命令行。它有两种形式:
#define 宏名字 替代正文
#define 宏名字(参数列表) 替代正文
注意替代正文延续到本行结束,如果要写多行的替代正文,可以在行末写一个反斜线符号"/"(注意反斜线后面应紧跟换行符),这将使下一行继续被当作替代征文。
宏定义的替代正文可以是任何正文段,例如
#define SLD static long double
#define NOSTOP while(1)
#define min(A, B) ((A)<(B)?(A):(B))
宏定义从定义的位置开始起作用(也就是说可以在文件中任何位置进行宏定义),直到本源程序文件结束。不允许对一个宏名字重复定义。C语言提供了一个预处理命令#undef,其作用是取消已有的宏定义,该命令之后,原先的宏定义失效。
#undef 宏定义
关于宏定义的优缺点:
1. 有些宏展开后会引起对参数的多次计算,而从宏调用的形式上完全看不到这种情况。例如用上面定义的min计算
z = min(n++, m++); 展开之后是 z = ((n++)<(m++) ? (n++) : (m++)); 其中m, n被多次计算,根本不是我们最初想要的结果。
2. 有些人用宏定义替代函数定义。采用宏定义,相应的宏调用在定义的位置展开,可以避免执行程序中函数调用引起的额外开销,因此可以提高执行效率。另一方面宏展开会导致程序代码膨胀,使最后的可知性程序变大。
3. C语言的宏机制是一种非常简单的的正文替换机制。预处理过程中做的就是简单的正文替换。这里根本不检查程序的语法形式,更不会处理变量类型问题,所以出一些很难发现的错误。一般来说,在写程序时,如果存在其他可用的机制,比如枚举定义,常变量,函数等,我们最好使用其它机制。这样编译程序可以帮我们做更多的检查。
3. 条件编译命令:#if #else #elif #endif
这组命令实现条件编译,其作用是在源程序里划出一些片段,使用预处理程序可根据条件保留或丢掉一段,或者从几段中选择一段保留下来。其中#if和#elif要求一个能静态求出“整型值”的表达式作为参数,另两个没有参数。
1) #if 整型表达式
.../*代码片段, 条件成立时保留*/
#endif
2) #if 整型表达式
.../*代码片段, 条件成立时保留*/
#else
.../*代码片段, 条件不成立时保留*/
#endif
3) #if 整型表达式
.../*代码片段, 条件成立时保留*/
#elif 整型表达式
.../*代码片段, 条件成立时保留*/
#else
.../*代码片段, 条件都不成立时保留*/
#endif
上面这些命令都是在预处理时,根据条件选取其中的一段,而丢掉其他没有被选择的部分。这在写大型程序的时候非常有用。
4. 特殊谓词:defined。
C语言提供了一个特殊谓词defined,其使用形式有两种:
defined 标识符 或 defined(标识符)
当标示符是有定义的宏名字时,defined将得到1,否则就得0。这种表达式常被作为条件编译(上一条3中描述)的条件。
此外还有两个预处理命令#ifdef和#ifndef,它们相当于#if和defined组合的简写:
#ifdef 标识符 相当于 #if defined(标识符)
#ifndef 标识符 相当于 #if !defined(标识符)
——上述内容参考于《从问题到程序-程序设计与C语言概论》,裘宗燕