提示:本文时对C语言基础知识:预处理部分的回顾总结。
C预处理器(preprocessor) 在源代码编译之前对其进行一些文本性质的操作。
其主要任务包括删除注释、插入被#include 指令包含的文件的内容、定义,
替换由 #define 指令定义的符号以及确定代码的部分内容是否应该根据一些条件编译指令进行编译。
*预处理器符号 :它们是由预处理器定义的符号。它们的值或者是字符串常量,或者是十进制数字常量。
_ _ FILE _ _ :进行编译的源文件名,如“name.c”
_ _ LINE _ _ :文件当前行的行号,如 23
_ _ DATE _ _ :文件被编译的日期,如 “Jan 30 2020”
_ _ TIME _ _ :文件被编译的时间,如“21:49:32”
_ _ STDC _ _ :如果编译器遵循 ANSI C,其值为1,否则为定义。
#define 指令正式的描述:
#define name stuff
#define指令可以把任何文本替换到程序中。
如果定义中的 stuff 非常长,它可以分成几行,除最后一行外,每行的末尾都要加一个反斜杠。如下例:
#define DEBUG_PRINT printf( "File %s line %d:" \
" x=%d, y=%d, z=%d", \
__FILE__, __LINE__, \
x, y, z )
#define 机制包括一个规定,允许把参数替换到文本中,这种实现通常称为 宏(macro) 或 定义宏(defined macro) 。宏的声明方式如下:
#define name(parameter-list) stuff
其中,parameter-list(参数列表)是一个由逗号分隔的符号列表,它们可能出现在 stuff 中。参数列表的左括号必须与 name 紧邻。如果两者之间有任何空白存在,参数列表就会被解释为 stuff 的一部分。
当宏被调用时,名字后面是一个由逗号分隔的值的列表,每个值都与宏定义中的一个参数相对应,整个列表用一对括号包围。当参数出现在程序中时,与每个参数对应的实际值都将被替换到 stuff 中。
#define CALCULATE(x, y) (x * (y + 1))
提示:所有用于 对数值表达式进行求值的宏定义都应该加上括号 ,避免在使用宏时,由于参数中的操作符或邻近的操作符之间不可预料的相互作用。
宏非常频繁地用于执行简单的计算,比如:
#define MAX( a, b ) ( (a) > (b) ? (a) : (b) )
使用宏比使用函数在程序的规模和速度方面都更胜一筹。并且宏于数据类型无关,而函数必须声明为一种特定的类型。
宏的副作用 :可能会在表达式求值时出现永久性的效果(每次结果都不同)。例如:x++
#define MAX( a, b ) ( (a) > (b) ? (a) : (b) )
...
x = 1;
y = 2;
z = MAX( x++, y++ ); //它增加了 x 的值,在下一次执行时,将产生一个不同的结果。
宏的命名约定 :一般是把宏名全部大写。
#undef 预处理命令用于 移除一个宏定义。
#undef name
许多C编译器提供了一种能力,允许你在命令行中定义符号,用于启动编译过程。
例如:在编译程序是,ARRAY_SIZE的值可以在命令行中指定。
int array[ARRAY_SIZE];
在UNIX编译器中, -D 选项可以完成这项任务;
-Dname 将name的值定义为 1
或者
-Dname = stuff 将该符号的值定义为等号后面的stuff
gcc -DARRAY_SIZE=100 prog.c
提供符号命令行定义的编译器通常也提供在命令行中去除符号的定义:
在UNIX编译器上, -U 选项用于执行这项任务。
条件编译(conditional compilatoin) :可以选择代码的一部分是被正常编译还是完全忽略。
#if、#elif、#else、#endif
#if constant-expression
statements
#elif constant-expression
other statements ...
#else
other statements
#endif
测试一个符号是否被定义:
#ifdef、#ifndef
在嵌套指令中,为每个 #endif 加上一个注释标签是很有帮助的,标签的内容就是 #if (或#ifdef)后面的那个表达式。例如:
#ifdef OPTION1
lengthy code for option1;
#else
lengthy code for alternative;
#endif /* OPTION1 */
#include 指令是另一个文件的内容被编译,就像它实际出现在#include指令出现的位置一样。
这种替换执行的方式原理为:预处理器删除这条指令,并用包含文件的内容取而代之。
提示:当头文件被包含时,位于头文件内的所有内容都要被编译,这个开销只是在程序被编译时才存在,所以对运行的效率并无影响。
编译器支持两种不同类型的 #include 文件包含:函数库文件和本地文件。
语法如下:
#include
对于filename,并不存在任何限制,不过根据约定,标准库文件以一个 .h 后缀结尾。
语法如下:
#include "filename"
处理本地头文件的一种常见策略是在源文件所在的当前目录进行查找,如果该头文件并未找到,编译器就像出查找函数库头文件一样在标准位置查找本地文件。
在一个将被其他文件包含的文件中使用 #include 指令是可能的。
标准要求编译器必须支持至少 8 层的头文件嵌套。
嵌套 #include 文件有两个不利之处:
1、很难判断源文件之间的真正依赖关系。
2、一个头文件可能会被多次包含。
多重包含在绝大多数情况下出现在大型程序中,它往往需要使用很多的头文件,因此要发现这种情况并不容易,但是我们可以使用 条件编译。
#ifndef _HEADRNAME_H
#define _HEADRNAME_H 1
/*
** All the stuff that you want in the header file
*/
#denif
#error 指令允许你生成错误信息。
#error text of error message
使用如下例:
#if defined( OPTION_A )
stuff needed for option A
#elif defined( OPTION_B )
stuff needed for option B
#elif defined( OPTION_C )
stuff needed for option C
#else
#error No option selected! //无操作则发送报错信息“No option selected!”
#endif
#line 指令通知预处理器 number 是下一行输入的行号。
#line number "string"
可选部分"string",预处理器将它作为当前文件的名字。
此指令将修改__LINE__符号的值,如果加上可选部分,它还将修改__FILE__符号的值。
此指令常用于把其他语言的代码转换为 C 代码的程序。
#progma 指令是另一种机制,用于支持因编译器而异的特性。
无效指令(null directive)# 就是一个 # 符号开头,但是后面不跟任何内容一行。这类指令只是被预处理器简单地删除。