预处理详解(完结篇)
一 什么是预处理
有许多文件中都内容我们是看不懂的,那怎么才能令我们看得懂呢?其实我们的系统拿到各种文件后,要进行一系列的操作过程,才能将文件转化成我们能够看得懂的信息。而翻译环境能够把源代码转换成可执行的机器指令,这个过程主要有编译和链接两个过程组成。
编译链接的过程:
预处理就是编译和链接过程中的一个阶段。在预处理阶段,源文件和头文件都会被处理成.i为后缀的文件,接下来我将详解预处理相关知识。
二 预处理指令
1 预定义符号
预定义符号:
1 __FILE__ //进⾏编译的源⽂件2 __LINE__ //⽂件当前的⾏号3 __DATE__ //⽂件被编译的⽇期4 __TIME__ //⽂件被编译的时间5 __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义注意:需要注意的是,__STDC__无法在vs中使用。
举个例子:
输出结果:
2 #define 定义常量
# define name stuff| |名字 内容
2.2 举个例⼦:
3 #define定义宏
3.1 概念:#define机制包括了⼀个规定,允许把参数替换到⽂本中,这种实现通常称为宏(macro)或定义宏(define macro)。
那我们来段代码感受一下宏/定义宏:
我们定义了一个宏ADD(a,b),又因为宏允许把参数替换到⽂本中,所以这个宏ADD(a,b)可以计算传入宏的两个参数a,b的和
3.2 宏的申明⽅式:
# define name( parament-list ) stuff其中的 parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中注意:参数列表的左括号必须与name紧邻,如果两者之间有任何空⽩存在,参数列表就会被解释为stuff的 ⼀部分。
3.3 使用宏/定义宏可能遇见的错误
示例:
输出结果:
从输出结果上来看,这个结果很令人出乎意料我们想得到6*7的结果,那为什么结果是13呢?这就要了解一下宏/定义宏的运行原理:其实,我们在把两个参数传过去后,(其原理是将参数在不计算的情况下直接传参传参过去)计算的其实是5+1*3+1,根据运算法则,,先算乘法再算加法,所以输出结果为13。
那怎么解决呢?我们想如果先计算a+1,在计算b+1,再计算a+1的结果*b+1的结果,这时候我们用括号把a+1括起来先计算不就可以吗?
这时候我们就得到我们想要的结果了。
4 带有副作⽤的宏参数
4.2什么是副作⽤
假如我们想令一个数+1,但是这个数自己的大小不能变:
1 x+ 1 ; //不带副作⽤ x未变2 x++;//带有副作⽤ x变大了
4.2带来的后果:当宏参数在宏的定义中出现超过⼀次的时候,如果参数带有副作⽤,那么你在使⽤这个宏的时候就可能出现危险,导致不可预测的后果。
示例:
输出结果:
我们来分析一下为什么会出现这个结果,首先我们(利用宏原理)先把宏的值全都替换掉得到:
printf("%d %d %d",a,b, ((a++) > (b++) ? (a++) : (b++)));
再分析其过程: a++的特性是先用后加,所以我们实际比较的是4和5两个数。再比较完之后,a变成5,b变成6。然后返回值为b++。同样是先用后加,所以先返回b的值6,然后再自增1,现在b等于7。
5 宏替换的规则
6 宏函数的对⽐
我们看到这应该已经能发现了,宏和函数功能及其相似,那他们两个到底谁更好呢?

三 #和##
1 #运算符
1.1作用:#运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中。 #运算符所执⾏的操作可以理解为”字符串化“。
1.2形式:
# define PRINT(n) printf( "the value of " #n " is %d" , n);
1.3理解#运算符
示例:比如当我们有⼀个变量 int a = 10; 的时候,我们想打印出: the value of a is 10 .
或者当我们有⼀个变量float b=5.0;的时候,我们想打印出: the value of a is 5.0 .
运行结果:
从结果上其中n变量并未替换这是为什么呢?因为宏将n变量识别为一个字符,所以我们这里要使用#运算符将宏的⼀个参数转换为字符串字⾯量。那我们运用#运算符试一下。
运用#运算符:
运算结果:
从结果分析我们运用 #运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中,从而防止变量识别为字符。
2 ##运算符
2.1作用:## 可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的⽂本⽚段创建标识符。 ## 被称为记号粘合。这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的。
示例:
这里我们就是把type和MAX合并成了一个函数名,如果未使用##运算符其中的type_MAX不会理解为int_MAX,而是理解为type_MAX。实际上我们很少用到#操作符和##操作符,但是当我们看到#操作符和##操作符时要知道这是什么意思。
3 命名约定
因为⼀般来讲函数的宏的使⽤语法很相似。为了方便我们自己后续方便分清函数和宏
把宏名全部⼤写函数名不要全部⼤写
四 #undef
1作用:我们可以用#undef来移除一个宏定义
2示例:
未使用#undef:
使用#undef:
五 条件编译
1作用:在编译⼀个程序的时候我们如果使用条件编译可以决定⼀条语句(⼀组语句)编译或者放弃。
2分类:
1 # if 常量表达式//...# endif//常量表达式由预处理器求值。2 多个分⽀的条件编译# if 常量表达式//...# elif 常量表达式//...# else//...# endif3判断是否被定义
第一种判断已定义:
# if defined(symbol) 或# ifdef symbol第二种 判断未定义::# if !defined(symbol) 或# ifndef symbol
4. 嵌套指令# if defined(OS_UNIX)# ifdef OPTION1
3 示例
因为#define MAX已定义所以会打印hehe1。
#if和if的区别是,如果不满足条件的话,#if后的语句根本就不进行编译,这样会节省系统执行的时间。