孔乙己学C语言(3)

1.3  预编译指令

1.3.1文件包含

实际上,绝大多数的汽车厂商都不能生产出完整的产品。为了生产一辆汽车,厂商需要的所有配件来源大致分成了三个部分:第一部分是厂商自己生产的,比如车体,方向盘,厂商完全了解如何生产和使用这部分配件。第二部分是其他厂家生产的,比如轮胎,但是生产技术不是保密的,即使不是自己生产的,厂商也完全了解如何生产和使用。第三部分也是其他厂家生产的,厂商可以使用,但是不了解生产的工艺。这往往是关键的组成部分,比如发动机。

问题描述:

完成一个程序时往往也同样会需要其他程序的协助。如果把一个程序看作汽车,在输入输出操作时,我们经常使用的“printf()”其实可以看做是一个“发动机”,本身我们并不知道这个函数是怎么实现的,但是我们知道如何使用,所做的只是把这个“发动机”放到合适的位置就可以了。由于生产配件的厂商数量很多,所以每当我们使用这些配件的时候,需要标明配件的生产厂商。程序的配件生产厂商实际上就是“库文件”,每一个它们生产的配件就是一个库函数。

实例分析:

/*example1_3_1.c*/

 

int main(void){

……

return 0;

}

/*example1_3_2.c*/

#include<stdio.h>

int main(void){

……

return 0;

}

对应的汇编代码

/*example1_3_1.c*/

 

int main(void){

00411350  push        ebp 

00411351  mov         ebp,esp 

00411353  sub         esp,0C0h 

00411359  push        ebx 

0041135A  push        esi 

0041135B  push        edi 

0041135C  lea         edi,[ebp-0C0h] 

00411362  mov         ecx,30h 

00411367  mov         eax,0CCCCCCCCh 

0041136C  rep stos    dword ptr es:[edi] 

         return 0;

0041136E  xor         eax,eax 

}

/*example1_3_2.c*/

#include "stdio.h"

int main(void){

00411350  push        ebp 

00411351  mov         ebp,esp 

00411353  sub         esp,0C0h 

00411359  push        ebx 

0041135A  push        esi 

0041135B  push        edi 

0041135C  lea         edi,[ebp-0C0h] 

00411362  mov         ecx,30h 

00411367  mov         eax,0CCCCCCCCh 

0041136C  rep stos    dword ptr es:[edi] 

         return 0;

0041136E  xor         eax,eax 

}

 

对这两个程序进行比较,第二个程序里多了一个#include "stdio.h"语句,但是在对两段程序进行汇编的时候却可以发现,两段程序的汇编代码是完全相同的。

还记得上一节提到过的C语言的编译阶段吗?#include <stdio.h>这条语句的处理实际上是发生在预处理阶段(第一阶段),而上面的汇编代码是在编译阶段(第二阶段)完成的。

对于#include <stdio.h>指令来说,它在预处理阶段完成了两个工作

1预编译器首先在计算机系统中找到<stdio.h>这个文件。

2预编译器在预编译阶段把程序中的“#include <stdio.h>”语句替换为找到的那个文件中内容。

需要注意的是,对于#include <stdio.h>文件中的内容,在这个阶段中是没有做任何处理,只是简单的替换而已。

比如这样的一个头文件MaxNum.h

/*MaxNum.h*/

int maxNum(int a, int b)

{

if(a>b)return a;

else return b;

}

 

/*example1_3_4.c*/

#include<maxnum.h>

int main(void)

{

int i=1,j=3;

maxNum(i,j);

return 0;

}

在经过预编译处理阶段以后, example1_3_2.c程序会被处理成下面这样:

int maxNum(int a, int b)

{

if(a>b)return a;

else return b;

}

int main(void)

{

int i=1,j=3;

maxNum(i,j);

return 0;

}

深入剖析

有时我们在编写程序时,会发现某一段代码会被反复的使用(比如求两个数中哪个更大),如果只是在同一个文件中被调用,那么写成函数形式就可以。可是有时需要在另外一个文件中调用这个函数呢,这种情形又如何处理呢?

其实上一节已经给出了解决的方法,就是将这个函数单独的写成一个头文件,然后在需要使用的时候,在程序的开始部分用#include<文件名>的方式把这个函数引入,就可以正常使用了。(可以参见上一节的程序/*example1_3_4.c*/

其实汽车工厂在使用发动机的时候,并不需要知道发动机是怎么生产出来的,他们所关心的只是这个发动机怎么使用。因此在装配汽车的时候,需要的只是一份使用说明书和一件发动机就可以了,头文件其实也是包含了说明书和产品两部分,函数中的声明就是一份说明书,函数的实现就相当于发动机。在使用的时候,这两者一般是分离的,例如上节中的MaxNum.h一般是分成声明和实现两部分的,如:

/*MaxNum.h,声明部分*/

int maxNum(int a, int b)

 

/*MaxNum.c,实现部分*/

int maxNum(int a, int b)

{

         if(a>b)return a;

         else return b;

}

这里面就完成了代码的实现与声明相分离。

实现这种分离其实还有另外一个好处,这里是不是有很多人依靠写代码为生呢?当他们辛辛苦苦的写完一些代码,通过调用这些代码可以完成某些功能。但是这些程序员也许根本不是开源的狂热追求者,甚者有些人可能还希望出售这些代码来还银行的房屋贷款。那么如果这些代码的实现如同上述的那些一样,每个人都可以看见,那么还有谁会出钱去购买这些代码呢?

显然,有人好像进入了两难的尴尬境地,怎样做才能做到两全其美呢?

函数的调用者只需要看到MaxNum.h声明部分,就可以正常的使用MaxNum这个函数的全部功能。如果我们能把MaxNum.c这部分代码变成一段可以执行,但是不可以查阅其中代码的文件,那么好像一切就可以迎刃而解了。

好在lib文件解决了这个问题,对于MaxNum.c可以把它编译成一个MaxNum.lib文件,这个文件在执行的时候和MaxNum.c是没有任何区别的,但是在阅读的时候,却是以2进制的格式显示的,试图把这种格式从新恢复成C语言是极难实现的,这就既完成了代码的可调用性,又完成了实现代码的隐蔽性,实现了两全其美。

总结

#include<file1.h>起的作用很简单,就是先找到了file1.h这个文件,然后在预编译的阶段用file1.h文件中的内容替换以上程序中的#include<file1.h>这条语句。

使用 #include包含文件的好处可以总结为以下几点:

1代码书写的可读性更高

2很多函数的编写一次以后,可以多次使用。

3实现了代码的实现与声明相分离。

扩展:

经常会出现#include<file1.h>#include”file2.h”这两种写法,这里<>””是有区别的。

这里举一个例子,当你在读大学的时候,突然有人通知你的一门考试出了问题,要你去找管理教务的李进老师,那么接下来会做什么呢?

肯定是去找这个李进老师了,问题是学院里有一个专门管理教务的老师,学校里还有专门的教务处,里面都是管理教务的老师!

那么先到哪里去找呢?

这个问题就和头文件的位置问题一样,通常头文件有两个常用的位置,一个是系统设定的目录(就好像学校里的教务处),另一个是当前工作的目录(就好像学院里的院办公室)。

把你陷入这个接下来不知去哪的困境的原因就是通知并没有说清楚到底是李进老师到底是哪个部门的。

C语言为了避免这个问题,做了这样一个约定,那就是“”和<>的使用,比如说系统的默认工作目录为:

C:\Program Files\Microsoft Visual Studio\VC98\Include\

当前的工作目录为:

D:\My Program \test \

如果你使用的是#include<MaxNum.h>,那么C语言编译器会在开发环境设定的搜索路径中去查找所需的头文件。

#include”MaxNum.h”通常首先在当前工作目录下搜索头文件,如果找不到的话,再到开发环境设定的路径去查找。

另外,在#include后面可以加上文件的路径(绝对路径和相对路径都可以的)。

例如#include “D:\My Program \test \MaxNum.h”

我们编写的头文件通常放在当前的工作目录,所以引用自己写的文件通常也会使用#include”file2.h”的方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值