不简单的Hello World之头文件

今天咱们就来讲说在程序界里无人不知无人不晓大名鼎鼎的Hello World,比如数学界里大名鼎鼎1+1=2,无数码农入坑正是从写这样一段代码开始的,今后踏入了万劫不复的深渊一发不可收拾。。。这段代码是这样写的,程序员

#include <stdio.h>面试

int main()编程

{微信

    printf("Hello World!\n");函数

    return 0;工具

}spa

很简单啊,有没有,咱们就喜欢写hello world啊,各类语言的都会写啊,写完一门语言的hello world就在简历上写下了让他在面试时痛不欲生的一句话:"精通**语言"。。扯远了,咱们首先来看看第一句话, "#include <stdio.h>",这句话究竟是什么意思呢?咱们为何要写这样一句话呢?操作系统

话说在编程界的上古时代,古老到尚未Android,iOS,甚至Windows,Linux尚未出现的蛮荒时代,那个时候写程序尚未include这种写法,想引用一个函数能够直接在文件开头列出函数原型,假如咱们在add.c中写了一个sum函数就像这样:.net

----------------------sum.c设计

int add(int a, int b)

{

    int c = a + b;

    return c;

}

咱们想在main.c中使用这个函数,能够这样来写:

--------------------main.c

int add(int a,int b);

int main()

{

    int sum;

    sum = add(1, 2);

    return 0;

}

这种古老的写法至今是正确的,实际上咱们使用的编译器在预编译完成后的main.c文件就是这样的,既然能够这样写那咱们为何要用include的写法呢?

让咱们再次回到编程界的上古时代,假设这个add函数是你写出来的巨牛巨拽巨拉轰以致于人人都爱用,因此在你参与的某个划时代的大项目中屡次被引用到,全部引种这个函数的地方都要加上刚才那函数原型的声明,做为实现这个函数的你隐隐约约以为本身项目成功后立刻就能够升职加薪,当上总经理出任CEO迎娶白富美今后走上人生巅峰。。

然而有一天做为add函数的撰写人你忽然灵光一现想到了一种更牛更拽更加拉轰闪瞎众人的写法,惟一一点不足之处就是要修改一下返回值类型。。那么接下来的状况就能够想而知了,那就是你须要把全部用到这个函数的地方的函数原型修改掉,假如你的划时代项目工程比较浩大,有500个.c文件其中250个用到了这个函数,想象一下你的老板要你找到出这250个文件而后依次修改。。你的心里应该是崩溃的。。

能直接修改到想撞墙有没有!!!

因此为了不你出现上面的情况,实现程序员能早点下班好好享受生活的美好愿望,伟大的计算机先驱们发明了include文件,这都是先驱们趟过的坑,用智慧汗水还有勤劳的双手发明出来的利器,它的做用是这样的,全部的函数原型放到include文件当中,编译的时候预编译器负责在include的位置展开include文件,所谓展开include文件就是把这个文件的内容copy到当前位置。这样每次编译的时候最新版的函数原型就在里面啦,先驱们是否是颇有智慧很伟大!

因此总结一下就是

那么include文件里到底有什么呢?

主要是函数原型

那什么又是函数原型呢,所谓函数原型就是返回值+函数名+参数,这三要素惟一的定义了一个函数原型,有了函数原型你就能够调用一个函数啦,为何要使用include这样的设计呢,我直接写一个函数原型而后引用不能够吗?固然是能够的,可是为何不直接这样写的,在本身写的玩具程序中固然是能够这样写的,可是在大型项目中若是这样作请参考上面的撞墙系列。。

include文件是如何处理的呢?

把这项工做委托给了编译器的一个组成部分也就是预编译器,所谓预编译器也就是真正开始编译前的准备阶段,就比如大厨作饭以前要准备好各类食材后才能够开始烹饪,而预编译器的工做就是在为编译器准备食材。这样你就能理解了吧,头文件展开还有宏展开都是在这个阶段由预编译器完成的。

因此include文件就比如一本书的目录,而每一个函数原型就比如其中的一个目录项,根据目录项(函数原型)就开始找到目录对应的真正内容了(.c文件),经过include文件咱们能够就能够引用别人的代码还有操做系统提供给咱们的服务啦,而不用本身一项一项的写函数原型了,是否是很方便,就像下面这样:

下面咱们就用一个例子来看看编译器展开include文件后是什么样的,

让咱们定义一个叫作sum.h的include文件,编辑

---------------------------sum.h

#include  " math.h"

int sum(int a, int b);

同时这个sum.h又include了math.h

---------------------------math.h

void math();

在main.c中引用了sum.h

--------------------------main.c

#include "sum.h"

int main()

{

   return 0;

}

那么编译器是怎么作的呢,首先找到sum.h(请思考编译器是怎么找到sum.h的呢), 而后编译器发现了sum.h还include了math.h,因此编译器又去找到了math.h,把math.h的内容在sum.h中展开,最后再把sum.h中的内容放到main.c中就是这样的效果啦

--------------------------main.c

void math();

int sum(int a, int b);

int main()

{

   return 0;

}

怎么样,你看明白了吗

使用gcc -E hello.c 这个命令你就能够看到一个hello.c真正展开后的样子啦。

因此这里想说的是咱们常用的工具已经帮助咱们完成了不少繁琐的事情,但咱们最好能了解这些规则,由于只有了解规则,才能更好的利用规则。

--------------------------------

Aha! 原来如此

在把源代码编译成机器指令的过程当中首先要完成的就是预处理,这个过程是由一个叫作预编译器(Preprocessor)的程序完成,其工做不只仅包括对include文件的处理还包括各类宏替换等。

在预编译阶段,当预编译器在遇到#include "abc.h"时仅仅找到abc.h文件而后把abc.h的内容展开到当前位置,你能够简单的理解为copy到当前位置,固然也会进行一些处理,可是这样理解没有问题。

在编译阶段,添加相应的头文件仅仅是让编译器帮你检查一下你是否是正确的使用了某个函数,这里的用对就是指函数三要素是否是相符。

关于编译的具体信息,我会在后面的文章中给你们详细介绍。

这里还有一个问题,就是编译器是怎么找到include文件的呢,请听下回分解。


本文分享自微信公众号 - 码农的荒岛求生(escape-it)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索