首页 > 学院 > 开发设计 > 正文

学习笔记---预处理

2019-11-11 04:42:35
字体:
来源:转载
供稿:网友

预处理(编译预处理)

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

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

含义:

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

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

示意图:

解析:

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

宏定义

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

分类:

不带参数:

语法:#define  标识符  字符串

应用法则:原样替换

典型应用:符号常量

代码示例:

#include <stdio.h>#include <stdlib.h>/*这个程序用来测试预处理中的不带参数的宏定义*/#define PI 3.1415926int 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;}结果:

解析:

定义PI3.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*bint 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.如上,S1S2看似差别不大。但得到的结果却完全不同。

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文件夹里存放着大量系统头文件可供引用。

库函数:

如上,在MinGWlib文件夹中,存放着大量的.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 DEBUGint 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 DEBUGint 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 1int 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,应当用这种方式定义自己的头文件。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表