关于编译过程与宏的知识
编译过程:
程序文件包括源文件(后缀为.c)、目标文件(后缀为.obj)、可执行程序(Windows环境下后缀为.exe);
一个源文件到可执行程序的历程:
编译器通过编译将源文件转换为目标文件,然后通过连接器(linker)将目标文件捆绑在一起,形成一个单一而完整的可执行程序。编译本身又分别有以下几个阶段,①预编译阶段,②编译③汇编;每个阶段各司其职,具体过程如下图所示。
define定义标识符:
举个栗子:#define PI 3.1415
使用#define定义一个标识符来表示一个常量,可以做到“一改全改”,减少工作量,一般与#include写在一起,都在函数的外面,在源文件进行预编译的时候就将这个临时的符号PI全部替换为3.1415,当需要改变这个常量的值时,就不需要对整个程序一个一个进行修改,只需修改宏定义中的常量即可。且当常量比较长时,使用宏就可以用较短的有意义的标识符来代替它,这样编程的时候就会更方便,不易出错。
因此,宏定义的优点就是方便和易于维护。
值得注意的地方:
1、宏定义标识符时不加’’;’’ 具体原因如下,如果定义了 #define PI 3.1415;
如下代码段:
if(a>0){
a=PI;
-------------->就会被替换成:a=3.1415;;
}
出现语法错误,编译器编译源文件就会出现错误。
2、
宏:
定义方式: #define name(parament-list) stuff
其中,参数的左括号与宏名称之间一定要紧挨,否则参数列表会被解释成stuff的一部分。
移除宏定义: #undef NAME
细微的变化会导致表示的结果不同,举个栗子:
① #define ADD(a,b) a+b
①使用宏给变量赋值:int ret=ADD(2,3);
那么ret的值为2+3=5;
②使用宏给变量赋值:int ret=ADD(2,3)*5;
那么ret的值为2+3*5=17;
②#define ADD(a,b) (a+b)
①使用宏给变量赋值:int ret=ADD(2,3)*5;
那么ret的值为(2+3)*5=25;
②使用宏给变量赋值,有如下代码:
int a=1;
int b=2;
int ret=ADD(a|b,a&b);
ret的值:ret=(1|2+1&2)=(1|3&2)=2;注:运算符+优先级高于|;
③#define ADD(a,b) (a)+(b)
使用宏给变量赋值,有如下代码:
int a=1;
int b=2;
int ret=ADD(a|b,a&b);
那么ret的值为(1|2)+(1&2)=3+0=3;
所以一定要认真想想自己期望的结果!用于数值表达式进行求值的宏定义都应该使用如上方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符中不可预料的相互作用而导致最终结果与预期的结果不同,毕竟千里之堤,溃于蚁穴。
由于宏与函数使用语法很相似,语言本身无法帮我们区分二者,于是大家约定俗成:将宏名全部大写,函数名不要全部大写。以下是 宏与函数的对比:
属性 | define定义宏 | 函数 |
---|---|---|
代码长度 | 每次使用都会被插入程序之中,除非宏比较短,否则可能大幅度增加程序的长度 | 函数代码只出现在一个地方,每次吃使用时都调用同一个地方的同一份代码即可 |
执行速度 | 更快 | 调用和返回存在额外开销,相对慢些 |
结果预测 | 由于操作符优先级的原因,表达式结果不易于预测 | 将返回值传给函数,表达式结果更易于预测 |
参数类型 | 与类型无关,但也就不够严谨 | 函数的参数必须声明为特定的类型 |
调试 | 不方便调试 | 可逐句调试 |
递归 | 不能递归 | 可以递归 |