学习笔记---预处理

    xiaoxiao2021-03-26  33

    预处理(编译预处理)

    预处理命令:C语言中以符号“#”开头的命令

    示例:#define...   #include...   #ifdef...

    含义:

    1.在对程序进行编译之前,根据预处理命令对程序进行相应处理。

    2.经过预处理后编译器才可以对程序进行编译等处理,得到可供执行的目标代码。

    示意图:

    解析:

    如图,源程序经过编译和连接生成可执行文件。而预处理命令将在编译之前被执行

    宏定义

    释义:可以用#define命令将一个指定的标识符(即宏名)来代表一个字符串

    分类:

    不带参数:

    语法: #define  标识符  字符串

    应用法则:原样替换

    典型应用:符号常量

    代码示例:

    #include <stdio.h> #include <stdlib.h> /*这个程序用来测试预处理中的不带参数的宏定义*/ #define PI 3.1415926 int main() { float r,l,s; printf("please enter r:"); scanf("%f",&r); l=2*PI*r; s=r*r*PI; printf(" l: %f \n s: %f \n",l,s); return 0; } 结果:

    解析:

    定义PI为3.1415926之后,在编译前将把代码段中所有PI都替换成3.1415926,之后再生成目标文件。这种方式能降低出错的概念,也能提高程序的可读性。

    注意:绝对不要用 #define PI 3.1415926;这样的形式!因为宏定义的法则是原样替换,所以会把分号也原样代入程序!在上述的示例程序中l的计算方程就会变成l=2*3.1415926;*r;一个语句中出现两个分号,就会带来致命的差错!

    带参数:

    语法:#define  宏名(参数表)  字符串

    例:

    #define S(a,b) a*b ... area=S(2,4); 解析:

    此处area=S(2,4)将在编译前被解析为:area=2*4

    代码示例:

    #include <stdio.h> #include <stdlib.h> /*这个程序用来测试预处理中的带参数的宏定义*/ #define S(a,b) a*b int main() { printf("矩形1面积;%d\n",S(2,4)); printf("矩形2面积:%.2f\n",S(2.3,4.5)); return 0; } 结果:

    解析:

    如上,程序成功计算出了结果。使用带参数的宏定义同样能降低出错的概念。同时大大提升代码的可读性。

    深度理解:

    带参数宏定义和函数的区别:

    1.函数在编译后会生成目标代码,且在程序运行到调用行,会跳转入函数中继续执行。而带参数的宏定义将在编译前被替换成一个常量表达式。在目标代码中也不会出现任何痕迹。

    2.函数的参数、返回值类型都是有严格定义的。而宏定义只是进行机械的替换,参数以及其计算的结果的数据类型都需要自行把握。

    带参数宏定义的易错点:

    代码示例:

    #include <stdio.h> #include <stdlib.h> /*这个程序用来测试预处理中的带参数的宏定义的性质*/ #define PI 3.1415926 #define S1(r) PI*r*r #define S2(r) PI*(r)*(r) int main() { float a,b; a=1; b=2; printf("test1:\nr=%.2f,area=%.2f\n",a+b,S1(a+b)); printf("test2:\nr=%.2f,area=%.2f\n",a+b,S2(a+b)); return 0; } 结果:

    解析:

    1.如上,S1和S2看似差别不大。但得到的结果却完全不同。

    2.宏定义的法则为原样替换,所以对于S1(1+2)将被替换为:PI*1+2*1+2。可以看到,因为计算符号优先级的原因,算式完全扭曲了我们希望得到的结果。

    3.S2所做的是,给每个参数都带上一个小括号,这就解决了S1所遇到的问题,并能够计算出正确的结果。事实上,这也是代码规范化的一部分。认识的这一点之后,在使用宏定义时,应当尽量给每个参数都加上小括号。以减少错误的发生。

    文件包含

    释义:一个源文件可以将另外一个源文件的全部内容包含进来。

    语法: #include  "文件地址"  或  #include  <文件地址>

    图示:

    如图,在file1.c中加入#include "file2.c"效果相当于将file2.c中所有的代码拷贝至当前位置。

    应用:将源文件与头文件分离,提高代码重用性。具体例子在:http://blog.csdn.net/aketoshknight/article/details/54837779大型程序基础---调用外部头文件部分

    特点——节省程序设计人员的重复劳动:

    1.将常用的一组固定常量的定义组成一个文件——方便,易修改。

    2.库函数的开发者将对被调用的函数的原型声明写入头文件,程序员只需要#include <头文件> ——大大简化了程序。

    3.一行#include ,相当于写几十、几百,甚至更多行内容,提高了效率。

    起源——模块化程序设计的产物:

    1.便于多个程序员分别编程。

    2.将公用的符号常量或宏定义等可单独组成一个文件,在其他文件的开头用包含命令包含该文件即可使用。

    3.公用的声明只写一次,除节省时间,更可观的是减少出错。

    include 命令的两种形式:

    1.#include <文件名>

    例:#include <stdio.h>

    特点:编译器将在系统目录(不同的操作系统可能有不同的系统目录,也可以在编译器的设置中自行修改系统目录的地址)中寻找要包含的文件,如果无法找到该文件,则给出出错信息。

    注:对于系统提供的头文件,既可用尖括号形式,也可用双撇号形式,但一般用尖括号形式的效率更高。

    2.#include "文件名"

    例:#include "mymodule.h"

    特点:编译器先按双引号中指出的文件路径和文件名查找(如果给出了E:test.c这样形式的路径,便会去该处寻找。如果如上所示只给出文件名,将默认在用户当前目录(程序所在的目录)中寻找)若找不到,再去系统目录中查找。若仍旧无法找到,则给出出错信息。

    注:若要包含的是用户自己编写的文件,一般保存在程序目录中,因此宜用双撇号形式。

    GCC编译器中的头文件和库函数:

    头文件:

    如上,MinGW便是一种GCC编译器,其中的include文件夹里存放着大量系统头文件可供引用。

    库函数:

    如上,在MinGW的lib文件夹中,存放着大量的.o目标文件,以及多种.o目标文件打包而成的.a文件(在代码连编的连接阶段,便是使用这里的文件和程序源文件的目标代码进行连接的)

    注:以上图示是GCC编译器的体系,而在VC++及VS中,目标文件将打包成.lib或.dll文件,而非.a文件。

    条件编译

    释义:根据需要,只编译程序中的某一部分。

    常用形式1:

    //define 标识符 #ifdef 标识符 程序段1 #else 程序段2 #endif 解析:

    ifdef可看成if define的简写。当所指定的标识符已经被#define命令定义过,则在程序编译阶段只编译程序段1,否则编译程序段2。#endif用来限定#ifdef命令的范围,其中#else部分可以省略。

    常用形式2:

    //define 标识符 #ifndef 标识符 程序段1 #else 程序段2 #endif 解析:

    ifndef可看成if not define的简写。和第一种正好相反。

    常用形式3:

    #if 常量表达式 程序段1 #else 程序段2 #endif 解析:

    和常用的if else条件选择结构的区别在于增加了代表预处理的#。如果常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。因此可以使程序在不同条件下完成不同的功能。

    代码示例1:

    #include <stdio.h> #include <stdlib.h> /*这个程序用来测试预处理中的条件编译*/ //宏定义常量DEBUG #define DEBUG int main() { int x=1,y=2; //ifdef 既if define如果宏定义了XX #ifdef DEBUG printf("x=%d,y=%d\n",x,y);//当宏定义DEBUG存在,这句代码将被编译。而如果宏定义DEBUG不存在,这句代码将不被编译。 #endif // DEBUG printf("x*y=%d\n",x*y); return 0; } 结果1:

    代码示例2:

    #include <stdio.h> #include <stdlib.h> /*这个程序用来测试预处理中的条件编译*/ //宏定义常量DEBUG //#define DEBUG int main() { int x=1,y=2; //ifdef 既if define如果宏定义了XX #ifdef DEBUG printf("x=%d,y=%d\n",x,y);//当宏定义DEBUG存在,这句代码将被编译。而如果宏定义DEBUG不存在,这句代码将不被编译。 #endif // DEBUG printf("x*y=%d\n",x*y); return 0; } 结果2:

    解析:

    1.如上示例,通过改变宏定义,运用#ifdef 即可控制代码中的某一部分是否被编译。

    2.虽然使用if  else 条件判断也能实现类似的效果,但此处的条件判断是预处理的一种。既这些代码将在源文件编译前就被执行,并且在编译后的.o目标文件中将不会出现这些代码

    3.这种方式适合在调试程序时使用,当调试完毕时,只需要取消对DEBUG的宏定义,即可一次性消除大量调试用代码对程序的影响。

    代码示例3:

    #include <stdio.h> #include <stdlib.h> /*这个程序用来测试预处理中的条件编译*/ #define R 1 int main() { float c,r,s; printf("input a number:\n"); scanf("%f",&c); #if R r=3.14159*c*c; printf("area of round is:%f\n",r); #else s=c*c; printf("area of square is:%f\n",s); #endif // R return 0; } 结果:

    解析:

    套用if else的使用经验,可以十分容易的理解。

    深入理解:

    文件包含的弊端:

    假设个文件A.h  B.c  C.c

    A.h:

    int test=10; .... B.c:

    include "A.h" int main() { .... } C.c:

    include "A.h" include "B.c" int main() { .... } 当编写较大的程序时,常会出现此种交叉包含的情况。这样,在C.c中就会出现A.h被多次包含的问题。既,A.h中的test变量被多次定义。导致编译出错。

    解决方案:

    A.h:

    #ifndef MYHEADER #define MYHEADER 1 #define PI 3.14 int NUM=3 ..... #endif 解析:

    1.如上,使用条件编译之后,完美的解决了这个问题。 2.这是一种编码规范的标准,为了减少代码的BUG,应当用这种方式定义自己的头文件。

    转载请注明原文地址: https://ju.6miu.com/read-662076.html

    最新回复(0)