那時的人們寫了不少的.c(.cpp)文件,漸漸地,人們發如今 不少.c(.cpp)文件中的聲明語句就是相同的,但他們卻不得不一個字一個字地重複地將這些內容敲入每一個.c(.cpp)文件。但更爲恐怖的是,當其中 一個聲明有變動時,就須要檢查全部的.c(.cpp)文件,並修改其中的聲明,啊~簡直是世界末日降臨!
終於,有人(或許是一些人)再不能忍受這 樣的折磨,他(們)將重複的部分提取出來,放在一個新文件裏,而後在須要的.c(.cpp)文件中敲入#include XXXX這樣的語句。這樣即便某個聲明發生了變動,也再不須要處處尋找與修改了---世界仍是那麼美好!
由於這個新文件,常常被放 在.c(.cpp)文件的頭部,因此就給它起名叫作「頭文件」,擴展名是.h.
今後,編譯器(實際上是預處理器)就知道世上除了.c(.cpp)文 件,還有個.h的文件,以及一個叫作#include命令。windows
雖而後來又發生不少的變化,可是這樣的用法一直延續至今,只是時日久遠了,人們便淡忘了當年的原因罷了。
提到了頭文件,就說說它的做用吧~
想 到了林銳GG寫的高質量C/C++編程上頭文件的做用的簡短描述:
(1)經過頭文件來調用庫功能。在不少場合,源代碼不便(或不許)向用戶公佈, 只要向用戶提供頭文件和二進制的庫便可。用戶只須要按照頭文件中的接口聲明來調用庫功能,而沒必要關心接口怎麼實現的。編譯器會從庫中提取相應的代碼。
(2) 頭文件能增強類型安全檢查。若是某個接口被實現或被使用時,其方式與頭文件中的聲明不一致,編譯器就會指出錯誤,這一簡單的規則能大大減輕程序員調試、改 錯的負擔。數組
預處理是編譯器的前驅,做用是把存儲在不一樣文件裏的程序模塊集成爲一個完整的源程序.
#include自己只是一個簡單的文件包含 預處理命令,即爲把include的後面文件放到這條命令這裏,除此以外,沒有其它的用處(至少我也樣認爲).安全
我對乾坤一笑兄的觀點,十分贊同,基礎的東東必定要弄明白.
我下面就乾坤一笑兄的例子作講,完備他的一些讓人疑惑不解的時候~數據結構
例子:函數
//a.h
void foo();
//a.c
#include "a.h" //個人問題出來了:這句話是要,仍是不要?
void foo()
{
return;
}
//main.c
#include "a.h"
int main(int argc, char *argv[])
{
foo();
return 0;
}
針對上面的代碼,請回答三個問題:
a.c 中的 #include "a.h" 這句話是否是多餘的?
1.爲何常常見 xx.c 裏面 include 對應的 xx.h?
2.若是 a.c 中不寫,那麼編譯器是否是會自動把 .h 文件裏面的東西跟同名的 .c 文件綁定在一塊兒?
3.第三個問題我給他改了一下:若是 a.c 中不寫include<>,那麼編譯器是否是會自動把 .h 文件裏面的東西跟同名的.c文件綁定在一塊兒?優化
下面是乾坤一笑的原話:spa
從C編譯器角度看,.h和.c皆是浮雲,就是更名爲.txt、.doc也沒有大的分別。換句話說,就是.h和.c沒啥必然聯繫。.h中通常放的是同 名.c文件中定義的變量、數組、函數的聲明,須要讓.c外部使用的聲明。這個聲明有啥用?只是讓須要用這些聲明的地方方便引用。由於 #include "xx.h" 這個宏其實際意思就是把當前這一行刪掉,把 xx.h 中的內容原封不動的插入在當前行的位置。因爲想寫這些函數聲明的地方很是多(每個調用 xx.c 中函數的地方,都要在使用前聲明一會兒),因此用 #include "xx.h" 這個宏就簡化了許多行代碼——讓預處理器本身替換好了。也就是說,xx.h 其實只是讓須要寫 xx.c 中函數聲明的地方調用(能夠少寫幾行字),至於 include 這個 .h 文件是誰,是 .h 仍是 .c,仍是與這個 .h 同名的 .c,都沒有任何須然關係。
這樣你可能會說:啊?那我平時只想調用 xx.c 中的某個函數,卻 include了 xx.h 文件,豈不是宏替換後出現了不少無用的聲明?沒錯,確實引入了不少垃圾 ,可是它卻省了你很多筆墨,而且整個版面也看起來清爽的多。魚與熊掌不可得兼,就是這個道理。反正多些聲明(.h通常只用來放聲明,而放不定義,參見拙著 「過馬路,左右看」)也無害處,又不會影響編譯,何樂而不爲呢?
翻回頭再看上面的3個問題,很好解答了吧?
它的解答以下:
答:1.不必定。這個例子中顯然是多餘的。可是若是.c中的函數也須要調用同個.c中的其它函數,那麼這個.c每每會include同名的.h,這 樣就不須要爲聲明和調用順序而發愁了(C語言要求使用以前必須聲明,而include同名.h通常會放在.c的開頭)。有不少工程甚至把這種寫法約定爲代 碼規範,以規範出清晰的代碼來。
2.答:1中已經回答過了。
3.答:不會。問這個問題的人絕對是概念不清,要不就是想混水摸魚。很是 討厭的是中國的不少考試出的都是這種爛題,生怕別人有個清楚的概念了,絕對要把考生搞暈。
over!
在此裏要明確一點,編譯器是按照編譯單元進行編譯的,所謂的編譯單元,是指一個.c文件以及它所include的全部.h文件.最直觀的理解就是一 個文件,一個工程中能夠包含不少文件,其中有一個程序的入口點,即咱們一般所說的main()函數(固然也能夠沒有這個函數,程序照樣能啓動,詳細見個人 blog中).在沒有這個程序入口點的狀況下,編譯單元只生成目標文件object file(.o文件,windows下叫作.obj).
這個例子中總共包含了二個編譯單元,分別是a.c,main.c,按照我所說的,在編譯階段只是生成各自的.o文件.這個階段不和其它的文件發生任 何的關係.
而include這個預處理指令發生在預處理階段(早先編譯階段,只是編譯器的一個前驅處理程序).
.h .c不見得是浮雲,脫離了編譯器談這些沒有任何的意義,拋開更深層次的這些,好比說,OS如何啓動這個文件,PE結構(linux 下爲elf)等等
編譯器首先要識別這個文件纔可能去編譯它,這是前提.若是你改了它的擴展名那麼你的編譯器還能認識它嗎~上升到一個更高的層次上 看待這個問題,XX兄說的也不錯~我想XX兄說的意思就是二者不可由於名字相同就認爲二者有什麼關係,名字是能夠隨便的~
二者之間的聯繫,我在前 面說過了,是因爲歷史的緣由形成的,再加上人的習慣,我想誰也不想多去記那麼多文件名吧.(拿我舉個例子,一個數
據表若是多於30個字段,我就覺 得頭大了,如今弄的表有的多達上百個字段,真但願那位高人研究出什麼好的方法來~,也讓咱們的世界美好一些~)
乾坤一笑的第三個問題頗有表明性,屢次在網上看到,如今的編譯器絕對沒有那麼智能,並且也沒有必須那麼作.下面咱們主要聊聊編譯器的處理過程.(我 想初學者有疑問的正在於此,便是對於編譯過程.h .c(.cpp)的變化不太瞭解,)
下面我說舉個簡單的例子來聊聊~
例子以下:
//a.h
class A
{
pubic:
int f(int t);
};
//a.cpp
#include "a.h"
int A::f(int t)
{
return t;
}
//main.cpp
#include "a.h"
void main()
{
A a;
a.f(3);
}
在預處理階段,預處理器看到#include "文件名"就把這個文件讀進來,好比它編譯main.cpp,看到#include "a.h",它就把a.h的內容讀進來,它知道了,有一類A,包含一個成員函數f,這個函數接受一個int型的參數,返回一個int型的值。再往下編譯很 容易就把A a這行讀懂了,它知道是要拿A這個類在棧上生成一個對象。再往下,它知道了下面要調用A的成員函數f了,參數是3,因爲它知道這個函數要一個整形數用參 數,這個3正好匹配,那就正好把它放到棧上,生成一條調用f(int)函數的指令(通常多是一句call),至於這個f(int)函數到底在哪裏,它不 知道,它留着空,連接時再解決。它還知道f(int)函數要返回一個int,因此也許它也爲這一點作好了準備(在例子中,咱們沒用這個返回值,也許它就不 處理)。再往下到文件末尾了main.cpp編譯好了,生成了main.obj。整個編譯過程當中根本就不須要知道a.cpp的內容。
同理,編譯器 再編譯a.cpp,把f()函數編譯好,編譯a.cpp時,它也不用管別的,把f()編譯好就好了。生成了a.obj。
最後一步就是連接的階段 了,連接器把項目中全部.cpp生成的全部.obj連接起來,
在這一步中,它就明確了f(int)函數的實現所在的地址,把main.obj中空 着的這個地址位置填上正確的地址。最終生成了可執行文件main.exe。
明白了嗎?不明白那就多說幾句了,咱們在學編譯原理的時候都知道,編譯器是分階段進行的,每個階段將源程序從一種表示轉換成另外一種表示,通常狀況 下都進行以下順序:源程序->詞法分器->語法分析器->語義分析器->中間代碼生成器->代碼優化器->代碼生成 器->目標程序.
其中這中間6項活動都要涉及的兩項主要活動是:符號管理器與錯誤處理器.
歸根緣由,這裏有一個叫作符號表的東東在 裏面讓你着魔同樣不明白,其實符號表是一個數據結構.編譯器的基本一項功能就是要記錄源程序中使用的標識符並收集與每一個標識符相關的各類屬性信息.屬性信 息代表了該標識符的存儲位置/類型/做用域(在那個階段有效)等信息,通俗的說一下就是,當編譯器看到一個符號聲明時,例如你的函數名它就會把它放到這個 符號表中去登記一下~符號表裏存放着你的函數的入口地址,參數個數,返回信息等等一堆東西~而在聯接階段主要是處理工程中的符號表與調用對應處理關係,即 咱們一般所說的解引用.
通過前面的,不知明白與否?
最後引用一下XXX兄的結尾三點:搞清楚語法和概念說易也易,說難也難。竅門有三點:1.不要暈着頭工做,要抽空多思考思考,多看 看書; 2.看書要看好書,問人要問強人。爛書和爛人都會給你一個錯誤的概念,誤導你; 3.勤能補拙是良訓,一分辛苦一分才;