C预处理器处理程序的源代码,在编译器之前运行,通常以"#"开头。
C语言的预处理主要有三个言而的内容:
1) 宏定义和宏替换;
2) 文件包含;
3) 条件编译。
1. 宏定义和宏替换
"宏"是借用汇编语言中的概念,为的是在C语言程序中方便的作一些定义和扩展。这些语句以#define开头,分为两种:符号常量的宏定义和带参数的宏定义。
1) 符号常量的宏定义和宏替换
1 #define 标识符 字符串
其中标识符就称为宏名称,注意宏定义末尾不加分号。
由于预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,故预处理是不做语法检查的。且宏定义不分配内存,变量定义才会分配内存。
2) 带参数的宏定义和宏替换
1 #define 标识符(参数列表) 字符串
对带有参数的宏定义进行替换时,不仅对宏标识符作字符串替换,还必须作参数的替换。
为了避免宏替换时发生错误,宏定义中宏参数应加上括号。
【示例】有如下一段代码:(2011腾讯)
1 #define ADD(x, y) x+y 2 int m = 3; 3 m += m*ADD(m, m);
则m的值为( )
A. 15 B. 12 C. 18 D. 58
【解答】A。 m+=m*ADD(m,m)将会被替换为m+=m*m+m,故m=15。
宏替换的本质很简单——文本替换。关于宏定义与宏替换请注意以下几点:
1) 宏名一般用大写,宏名和参数的括号间不能有空格,宏定义末尾不加分号;
2) 宏替换只作替换,不做语法检查,不做计算,不做表达式求解;
3) 宏替换在编译前进行,不分配内存,函数调用在编译后程序运行时进行,并且分配内存;
4) 函数只有一个返回值,利用宏则可以设法得到多个值;
5) 宏替换使源程序变长,函数调用不会;
6) 宏替换不占运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值)。
注意:应尽量少使用宏替换。在C++中,宏替换实现的符号常量功能由const、enum代替,带参数的宏可由模版内联函数代替。
2. 文件包含
#include 接受以下两种形式:
1) #include <standard_header>
2) #include "my_file.h"
如果头文件名括在尖括号(<>)里,那么认为该头文件是标准头文件。编译器会在预定义的位置集合中查找头文件,这些预定义的位置可以通过设置查找路径环境变量或者通过命令行选项来修改。使用的查找方法因编译器的不同而差别迥异。如果头文件在一对引号里,那么认为它是非系统头文件,非系统头文件的查找通常开始于源文件所在的路径。
3. 条件编译
提供条件编译措施使同一源程序可以根据不同编译条件(参数)产生不同的目标代码,其作用在于便于调试和移植。
条件编译控制语句有不同形式:
1 #if/ifdef/ifndef 2 #elif 3 #else 4 #endif
文件包含需要考虑避免多重包含的问题,下面介绍如何利用条件编译语句避免多重包含。
在编写头文件之前,我们需要引入一些额外的预处理设施。预处理器允许我们自定义变量。预处理器变量的名字在程序中必须是唯一的。任何与预处理器变量相匹配的名字的使用都关联到该预处理器变量。
为了避免名字冲突,预处理骂人变量经常用全大写字母表示。
预处理器有两种状态:已定义或未定义。#define指示接受一个名字并定义该名字为预处理器变量。#ifndef检测指定的预处理器变量是否未定义。如果预处理变量未定义,那么跟在其后的所有语句都被处理,直到出现#endif。
可以使用这些设施来预防多次包含同一头文件:
1 #ifndef SALESITEM_H 2 #define SALESITEM_H 3 // 此处是某个类的定义与相关函数定义 4 #endif
其中,条件指示#ifndef SALESITEM_H的作用是测试SALESITEM_H预处理器变量是否未定义。如果SALESITEM_H未定义,那么#ifndef测试成功,跟在#ifndef后面的所有行都被执行,直到发现#endif。相反,如果SALESITEM_H已定义,那么#ifndef指示测试为假,该指示和#endif指示间的代码都被忽略。
为了保证头文件在给定的源文件中只处理过一次,我们首先检测#ifndef。第一次处理头文件时,测试会成功,因为SALESITEM_H还未定义。下一条语句定义了SALESITEM_H。那样,如果我们编译的文件恰好又一次包含了该头文件,#ifndef指示会发现SALESITEM_H已经定义,并且忽略该头文件的剩余部分。