预处理是指在系统对源程序进行编译之前,对程序中某些特殊的命令行的处理,预处理程序将根据源代码中的预处理命令修改程序。使用预处理功能可以改善程序的设计环境,提高程序的通用性、可读性、可修改性、可调试性、可移植性和方便性,易于模块化。
相关项:
◆预处理器(Preprocessor)
预处理器是一个文本处理程序,它在程序编译的第一个阶段处理源代码的文本。当然预处理器不只是编译之前才被调用处理源代码,它也可以被其他程序单独的调用以实现文本的处理。
◆预处理命令
预处理命令有三类:宏定义、文件包含和条件编译。 下面是常见的预处理命令:
#include,文件包含,用于把指定的文件内容包含到所在文件的当前位置处。
#define 和 #undef,分别用于定义和取消定义条件编译符号。
#if、#elif、#else 和 #endif,用于按条件跳过源代码中的节。
#line,用于控制行号(在发布错误和警告信息时使用)。
#error 和 #warning,分别用于发出错误和警告,可以在编译时给出用户自定义的错误警告信息,这通常与条件编译一起用来报告可能出现错误和警告的地方。
#region 和 #endregion,用于显式标记源代码中的节。
预处理有以下特点
1、预处理指令总是占用源代码中的单独一行,并且总是以 # 字符和预处理指令名称开头且结尾不加分号。# 字符的前面以及 # 字符与指令名称之间可以出现空白符。
2、预处理命令可以放在程序中的任何位置,其有效范围是从定义处开始到文件结束。
3、包含 #define、#undef、#if、#elif、#else、#endif 或 #line 指令的源代码行可以用单行注释结束。在包含预处理指令的源行上不允许使用带分隔符的注释(/* */ 样式的注释)。
一、宏定义(#define)
简单的说宏就是替换,其分两类:简单的字符替换和带参数的宏替换。
1、字符替换
(1)格式:#define 标识符 字符串
其中,标识符为宏名,字符串为宏替换体。例子:#define N 10
(2)功能:编译前预处理程序将程序中该宏定义之后出现的所有标识符(宏名)用指定的字符串进行替换。
(3)使用注意:
①宏定义仅仅是符号替换,不是赋值语句,因此不做语法检查。
②宏名通常用大写字母。双引号中出现的宏名不替换。
③可用#undefine提前结束宏名的使用。
④使用宏定义可嵌套,即后定义的宏中可以使用先定义的宏。
(4)使用宏的优点:
①输入程序时及修改时可节省很多操作。
②宏定义后可多次使用,因此使用宏可增强程序的易读性和可靠性。
③使用宏系统不需要额外的开销,因为宏所代替的代码只在宏出现的地方展开,因此并不会引起程序的跳转。
2、带参数的宏
进行宏替换时可像函数那样通过实参与形参传递数据, 增加程序的灵活性。
(1)格式:#define 标识符(形参表) 形参表达式
例:#define S(a,b) (a>b)?(a):(b)
(2)功能:预处理程序将程序中出现的所有带实参的宏名,展开成由实参组成的表达式。
(3)使用注意:
① 宏名与括号之间不可以有空格。
②有些参数表达式必须加括号,否则在实参表达式替时会出现错误。
(4)带参数宏与函数的区别:
① 函数的形参与实参要求类型一致,而宏替换不要求类型。
② 函数只有一个返回值,宏替换可能有多个结果。
③ 函数影响运行时间,宏替换影响编译时间。
④ 使用宏有可能给程序带来意想不到的副作用。
二、文件包含(#include)
文件包含是将一个指定文件的内容完全包含到当前文件的当前位置中。
(1)格式:
格式1:#include<文件名>
格式2:#include"文件名"
(2)功能:用指定的文件名的内容代替预处理命令。
(3)两种格式的区别:
格式1定义时,预处理程序在C语言编译系统定义的标准目录下查找指定的文件。
格式2定义时,预处理程序首先在引用被包含的头文件的源文件所在的目录中寻找指定的文件,如没找到,再按系统指定的标准目录查找。
为了提高预处理程序的搜索效率,通常对用户自定义的非标准文件使用格式2,对使用系统库函数等标准文件使用格式1.
文件包含可以嵌套使用,但通常都要用条件编译防止重复包含,此问题在下面举例说明。
三、条件编译
这里只对#ifndef及其在文件包含中的应用进行说明。主要是为了避免头文件被多次包含。
#ifndef x //先测试x是否被定义过
#define x //如果没有定义就定义x并执行下面的语句
...
#endif //如果已经定义了则执行#endif后面的语句
条件指示符#ifndef检查预编译常量在前面是否已经被定义。如果在前面没有被定义,则条件指示符的值为真,于是从#ifndef到#endif之间的所有语句都被包含进来进行处理。相反,如果#ifndef指示符的值为假,则它与#endif指示符之间的行将被忽略。条件指示符#ifndef 的最主要目的是防止头文件的重复包含和编译。
多文件编程中千万不要忽略了头件的中的#ifndef,这是一个很关键的东西。比如你有两个C文件,这两个C文件都include了同一个头文件。而编译时,这两个C文件要一同编译成一个可运行文件,于是问题来了,大量的声明冲突。
还有就是头文件一般只包含一些让不同的编译单元(.C文件)能够相互联系的声明而已,而函数的实现代码则是放在.c文件中。比如有如下的文件:
a.c 和对应的头文件a.h
b.c 和对应的头文件b.h
c.c 和对应的头文件c.h
main.c
如果把函数的实现代码也放到头文件中,比如在a.h中有函数的实现代码,而b.c和c.c都包含a.h,那么就会得到a.h中函数的多重实现,于是连接器就会提示出现了多重定义。再比如上面的文件都是正常的,即头文件中只有声明,.c文件中是函数的定义。当b.h和c.h都需包含a.h且c.h包含b.h,main.c包含c.h时,若编译时各个头文件不使用#ifndef预处理命令则会出现多重包含的提示,在大程序中就会出现大量的定义冲突、声明重复之类的错误。
所以还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你都要加上这个。一般格式是这样的:
#ifndef <标识>
#define <标识>
......
......
#endif
<标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的“.”也变成下划线,如:stdio.h
#ifndef _STDIO_H_
#define _STDIO_H_
......
#endif