程序员所编写的代码并不能被真正的编译器编译,需要一段程序把代码翻译一下。翻译的过程叫做预处理,负责翻译的程序叫做预处理器,被翻译的代码叫做预处理指令,以#开头的代码都是预处理指令。
可以使用以下命令查看预处理过程:
gcc -E code.c 把预处理的结果显示到终端上
gcc -E code.c -o code.i 把预处理结果存储到code.i预处理文件中
常用的预处理指令有文件包含(#include)、宏定义(#define)、条件编译(#ifdef)等。
文件包含(#include)
#include <> 从系统指定路径查找并导入头文件
#include “” 从当前路径下查找,如果找不到再从系统指定路径查找并导入文件
此外,可以通过编译参数指定查找路径 -I /path,也可以通过修改操作系统的环境变量来指定头文件的查找路径
定义宏(#define)
C语言源程序中允许用一个标识符来表示一个字符串,称为“宏”。被定义为宏的标识符称为“宏名”。在编译预处理时,对程序中所有出现的宏名,都用宏定义中的字符串去代换,这称为宏替换或宏展开。
宏定义是由源程序中的宏定义命令完成的。宏替换是由预处理程序自动完成的。
1)宏常量
定义: #define 宏名 数据
举个例子:
#include<stdio.h>
#define MAX 50
int main(int argc,const char* argv[])
{
for(int i = 0;i<MAX;i++)
{
printf("%d ",i);
}
}
在程序预处理时,会将所有的MAX替换成为 50等价于下面的循环
for(int i = 0;i<50;i++)
{
printf("%d ",i);
}
优点:
提高代码可读性、提高可扩展性(方便批量修改)、提高安全性
注意:一般宏名全部大写,末尾不加分号(因为分号也会被替换进入代码),由于宏相当于常量,因此不能在程序中赋值,会报错。
预定义的宏(常用)
宏名 | 作用 | 类型(占位符) |
---|---|---|
func | 获取函数名 | %s |
FILE | 获取文件名 | %s |
DATE | 获取日期 | %s |
TIME | 获取时间 | %s |
LINE | 获取行号 | %d |
2)宏函数
定义:#define 宏函数名(变量1、变量2…) 自定义操作
举个例子:
#include<stdio.h>
#define SUM(a,b) a+b
int main(int argc,const char* argv[])
{
printf("%d ",SUM(1,2));
}
宏函数不是真正的函数,只是替换,不检查参数类型,只是值替换。宏函数没有返回值,只有表达式的计算结果。
注意:定义宏常量、宏函数、不能直接换行,可以使用续行符\放在末尾换行,也可以使用大括号保护代码
3)宏的二义性
由于宏所处的位置,参数不同导致宏有不同的解释和功能,和我们预想的结果不同。这种叫做宏的二义性。
举个例子
#include<stdio.h>
#define SUM(a,b) a+b
int main(int argc,const char* argv[])
{
printf("%d ",SUM(1,2)*2);
}
运行结果是 5 而不是预想的 6 。为什么会这样呢?很简单,把宏代入替换就知道了。
#include<stdio.h>
#define SUM(a,b) a+b
int main(int argc,const char* argv[])
{
printf("%d ",1+2*2);
}
SUM(1+2)替换成为1+2,但是右边的2先与乘2运算,所以出现二义性,我们以为是6,但是事实上是5。
如何避免二义性
1、宏函数整体代码加小括号
解决整体与外部先运算问题
#include<stdio.h>
#define SUM(a,b) a+b
int main(int argc,const char* argv[])
{
printf("%d ",SUM(1,2)*2);
}
运行结果是5,预期结果是6
2、宏函数每个参数都加小括号
解决内部参数间先运算问题
#include<stdio.h>
#define SUM(a,b) a*b
int main(int argc,const char* argv[])
{
printf("%d ",SUM(1+2,2));
}
运行结果是6,预期结果是5
3、使用宏函数时不要使用自变运算符
避免变量频繁自变
#include<stdio.h>
#define SUM(a) a*a
int main(int argc,const char* argv[])
{
int a = 2;
printf("%d ",SUM(++a));
}
运行结果是16,预期结果是9
条件编译(#ifdef)
根据条件决定让代码是否参与最终的编译。
1) 头文件卫士:写在头文件中,防止头文件重复包含
#ifndef 宏名(头文件名全大写,_代替.)
#define 宏名
头文件内容
#endif //宏名
#ifndef 宏名 ,如果这个宏名未定义执行下面的语句
#define 宏名,定义这个宏(如果没定义则定义这个宏,一旦定义下一次就不会进入上面的判断)
#endif //宏名,结束最外面的ifndef,加上注释//宏名,让别人知道是和最外面的头文件卫士对应。
2)版本控制
#if 条件1
内容1…
#elif 条件2
内容2…
#else
内容3…
#endif
举个例子:
#include<stdio.h>
#define VERSION 4
int main(int argc,const char* argv[])
{
#if VERSION > 3
printf("最新版本");
#elif VERSION > 2
printf("勉强能用");
#else
printf("快升级");
#endif
}
可以简单的当作if语句使用,但是与if不同的是,不满足条件的语句甚至不会出现在编译结果中。