今天咱們就來講說在程序界裏無人不知無人不曉大名鼎鼎的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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。