C预处理器在预处理器在源代码编译之前,对其进行一些文本性质的操作,它的主要任务包括:
- 删除注释;
- 插入被#include指令所包含的的文件内容;
- 定义和替换由#define指令定义的符号;
- 确定代码的部分内容是否应该根据一些条件编译指令进行编译;
预定义符号
下表总结了预处理器定义的符号,它们的值是字符串常量或是十进制数字常量。
符号 | 样例值 | 含义 |
---|---|---|
FILE | "name.c" | 进行编译的文件名 |
LINE | 25 | 文件的当前行号 |
DATE | "jan 31 1997" | 文件被编译的日期 |
TIME | "18:04:30" | 文件被编译的时间 |
define
#define的一般形式如下:
#define name staff
有了这条指令,每当有符号name出现在这条指令后面时,预处理器就会把它替换成staff.
替换文本并不仅限于数值字面常量。#define指令,可以把任何文本替换到程序中,下面有几个例子。
#define reg register
#define do_forever for(;;)
#define CASE break;case
如果定义中的staff非常长,它可以分成几行,除了最后一行之外,每行都要加上一个反斜杠(\),如下面的例子:
#define DEBUG_PRINT printf("File % line %d:"\
"x=%d,y=%d,z=%d",\
__FILE__,__LINE__,\
x,y,z)
宏
#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏或定义宏,下面是宏的定义方式:
#define name(parameter_list) stuff
- 参数列表的左括号必须与name紧邻,如果两者之间出现空白,列表参数就会被解释为stuff的一部分。
- stuff每个参数加上括号以及整个stuff加上括号避免引起歧义,如下:
define DOUBLE(x) ((x)+(x))
函数与宏
宏的优点或缺点:
优点:
- 使用宏比使用函数在程序的规模和速度方面更胜一筹;
- 宏与类型无关;
- 使用宏可以完成函数无法完成的操作;
缺点:
- 每次使用宏,一份宏定义的代码的拷贝都将插入到程序中——除非宏非常短,否则使用宏大幅度增加程序的长度;
/*宏与类型无关的例子*/
#define MAX(a,b) ((a) > (b) ? (a) : (b))
/*使用宏可以处理函数无法处理的例子*/
#define MALLOC(n,type) ((type*)malloc((n) * sizeof(typpe)))
undef
这条指令用于移除一个宏,使用形式如下:
#undef name
如果一个现存的名字需要被重新定义,那么它的就定义必须使用#undef移除。
条件编译
在编译的过程中,如果我们可以选择某条语句或某组语句进行翻译或忽略将会很方便,条件编译能够很好的满足上述假想,有两种条件编译的形式:
- 第一种形式:
#if constant-expression
statements
#endif
- 第二种形式:
#if constant-expression
statements
#elif constant-expression
other statements...
#else
other statements
#endif
- constant-expression 常量表达式(字面值常量或是#define定义的符号),由预处理器进行求值;
- 如果constant-expression为真,那么statemens被正常编译,否则被忽略;
- elif的个数没有限制,每个constant-expression 只有当前面所有的常量表达式的值都为假时才会被编译,#else子句中的语句只有当前面的所有常量表达式的值都为假时才被编译,其它情况下都被忽略。
是否被定义
测试一个符号是否被定义也是可能的,它的使用形式如下:
- 第一组等价形式:
#if defined(symbol)
#ifdef symbol
- 第二组等价形式:
# if !defined(symbol)
#ifndef symbol
嵌套指令
条件编译的指令可以相互嵌套,如下:
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_of_option1();
#endif
#ifdef OPTION2
unix_version_of_option2();
#endif
#elif defined(OS_MDOS)
#ifdef OPTION2
msdos_version_of_option2();
#endif
#endif
文件包含
#include指令使另一个文件的内容被编译,这种替换的方式很简单:预处理器首先将这些指令删除,并用包含的内容取而代之,一个头文件被包含了多少次就实际被编译了多少次。
函数库文件的包含
函数库文件的包含使用下面的语法:
#include <filename>
本地文件包含
本地库文件的包含使用下面的语法:
#include "filename"
处理本地头文件的一种策略就是在源文件所在的当前目录进行查找,如果该头文件未找到,编译器就像查找函数库头文件一样在标准位置查找本地头文件。
嵌套文件包含
多重包含在绝大多数情况下出现于大型程序中,他往往需要使用很多头文件,避免多重包含可以使用下面这种编写方式来编写头文件
#ifndef _HEADERNAME_H
#define _HEADERNAME_H
/*
*All the stuff that you want in the header file
*/
#endif
当头文件第一次被包含时,它正常处理,再次包含,它的所有内容被忽略。