学习笔记---预处理

41 篇文章 0 订阅
16 篇文章 0 订阅

预处理(编译预处理)

预处理命令: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;
}
结果:


解析:

定义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*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.如上,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 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,应当用这种方式定义自己的头文件。








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值