C语言中预处理阶段
预处理阶段,预处理器执行包含指定头文件(将源文件中以"#include"包含的文件复制到编译的源文件中)、宏替换(用字面值替换"#define"定义的标识符)以及条件编译(根据“#if”后面的条件决定需要编译的代码),以#开头的命令行就是预处理器处理的对象。这些命令行的语法独立于语言的其他部分,它们可以出现在任何地方,其作用延续到所在翻译单元的末尾(与作用域无关)。
1.头文件定义
(1)基本概念
头文件当中一般包含条件编译、宏定义、函数声明、结构体定义、类型定义以及#开头的一些预处理命令等等。
**【注意】**头文件一般只进行声明,定义放入相应的源文件中。
test.h头文件
//条件编译
#ifndef _TEST_H_
#define _TEST_H_
//结构体定义
struct Student
{
char ch;
int num;
double score;
};
//定义新的类型
typedef struct Student STU;
//定义全局变量
int global;
//声明函数
void print_messaga(STU *p);
#endif
test.c
/*
#include <文件名> 的命令行,直接从系统路径中查找对应的头文件。
#include "文件名" 的命令行,先从当前路径下查找头文件,如果当前路径查找不到,就去系统路径当中查找。
*/
#include <stdio.h>
#include "test.h"
void print_message(STU *p)
{
printf("%c %d %lf\n", p->ch, p->num, p->score);
}
int main()
{
STU stu = {
'm', 12, 99.5};
print_message(&stu);
return 0;
}
运行结果:
(2)补充
(a)头文件对编译效率的影响
某产品做过一个实验,把所有函数的实现通过工具注释掉,其编译时间减少了不到10%,究其原因,在于头文件A包含了B,B包含了C,C包含了D,最终几乎每一个源文件都包含了项目组所有的头文件,从而导致了巨大部分编译时间都花在解析头文件上面了。
对编译影响最大的是预处理阶段解析头文件的过程,
因为这个过程可以拆分成这几个步骤:
1、搜索头文件位置
2、打开头文件
3、读入头文件
4、关闭头文件
所以头文件过多,或者头文件依赖(包含头文件)过多,是很耗时间的,降低编译的效率。
对于小项目来说,可能编译过程最耗时间;但对于大型工程项目,头文件绝对最耗时间的,对编译效率影响最大。
(b)文件包含#include
文件包含有两种格式:
• #include <文件名> 直接从系统路径中查找对应的头文件
• #include “文件名” 先从当前路径下查找头文件,如果当前路径查找不到,就去系统路径当中查找。
• 使用#include时,被包含文件可以是绝对路径,也可以是相对路径,总之,只要头文件的存放路径与当前源文件的关系正确即可。
一个源文件包含了一个头文件,这个源文件就可以使用该头文件定义的内容。
【注意】#include不仅仅能包含.h类型的头文件,理论上它可以包含任意类型的文件,例如包含一个.c文件等,但我们通常都用于包含.h类型的头文件。
2.宏定义
(1)基本概念
• 预处理器#define 定义的是不带数据类型的常数,只进行简单的字符替换。 在预编译的时候起作用,不存在类型检查。
• 宏定义,又称宏替换,用#define定义宏,指的是用字面值直接替换#define定义的标识符(用大写形式表示)。
一般形式:#define 标识符 字面值
【注意】字面值一般为整型/浮点型/字符型/字符串常量;标识符用大写形式表示,以和变量名作区分。
(2)宏定义常量
• #define 定义的宏常量可以直接使用。字面值一般为整型/浮点型/字符型/字符串常量);标识符为大写形式表示。
• 在预处理阶段直接将定义的宏进行文本替换
对上面的test.h做简单修改如下:
#include <stdio.h>
#include "test.h"
#define SEX 'm'
#define NUM 100
#define SCORE 99.5
#define PRINT "Show current message!"
void print_message(STU *p)
{
printf("%c %d %lf\n", p->ch, p->num, p->score);
}
int main()
{
STU stu = {
SEX, NUM, SCORE};
print_message(&stu);
printf("%s\n", PRINT);
return 0;
}
运行结果:
(3)宏定义的表达式(带参数和不带参数)
• #define 定义带参数的表达式称为宏函数,但没有任何调用开销,因为宏定义不分配内存 。并且#define 定义的表达式在有些时候比函数更加强大。
• 宏函数,其字面值是某种表达式,标识符用大写形式表示,后面有括号和对应的宏函数形参,宏函数形参没有数据类型,且宏函数用实参完全替代形参,不进行任何运算。
• #define 表达式很容易出错,一定要对整个表达式套上括号。注意,宏定义只是简单替换,不能出现递归定义。
代码分析:
#include <stdio.h>
#define MAX(a,b) (a > b?a:b)
int main()
{
printf("a & b of max is %d\n", MAX(100,10));
return 0;
}
(4)总结
• 预处理器直接对宏进行替换
• 预处理器不会对宏定义进行语法检查
• 宏定义时出现的语法错误,只能被编译器检测到
• 宏定义的效率高于函数调用
• 宏定义容易出错
3.条件编译
(1)基本概念
条件编译类似于C语言的if……else……,是一种预编译指示指令,用于控制是否预编译某段代码。在预编译阶段,将条件编译指令处理完了,就只剩下条件编译中为真的那段代码。
(2)条件编译的指令
(a)
#define TEST //undef TEST
#ifdef TEST //ifndef TEST
代码1
#else