C++中的頭文件和源文件

轉(zhuǎn)載文章:C++中的頭文件和源文件

一、C++編譯模式

通常咱圆,在一個C++程序中,只包含兩類文件——.cpp文件和.h文件功氨。其中序苏,.cpp文件被稱作C++源文件,里面放的都是C++的源代碼捷凄;而.h文件則被稱作C++頭文件忱详,里面放的也是C++的源代碼。
C+ +語言支持“分別編譯”(separate compilation)跺涤。也就是說匈睁,一個程序所有的內(nèi)容,可以分成不同的部分分別放在不同的.cpp文件里桶错。.cpp文件里的東西都是相對獨立的航唆,在編 譯(compile)時不需要與其他文件互通,只需要在編譯成目標文件后再與其他的目標文件做一次鏈接(link)就行了院刁。比如糯钙,在文件a.cpp中定義 了一個全局函數(shù)“void a() {}”,而在文件b.cpp中需要調(diào)用這個函數(shù)退腥。即使這樣任岸,文件a.cpp和文件b.cpp并不需要相互知道對方的存在,而是可以分別地對它們進行編譯狡刘, 編譯成目標文件之后再鏈接享潜,整個程序就可以運行了。
這是怎么實現(xiàn)的呢颓帝?從寫程序的角度來講米碰,很簡單窝革。在文件b.cpp中,在調(diào)用 “void a()”函數(shù)之前吕座,先聲明一下這個函數(shù)“void a();”虐译,就可以了。這是因為編譯器在編譯b.cpp的時候會生成一個符號表(symbol table)吴趴,像“void a()”這樣的看不到定義的符號漆诽,就會被存放在這個表中。再進行鏈接的時候锣枝,編譯器就會在別的目標文件中去尋找這個符號的定義厢拭。一旦找到了,程序也就可以 順利地生成了撇叁。
注意這里提到了兩個概念供鸠,一個是“定義”,一個是“聲明”陨闹。簡單地說楞捂,“定義”就是把一個符號完完整整地描述出來:它是變 量還是函數(shù),返回什么類型趋厉,需要什么參數(shù)等等寨闹。而“聲明”則只是聲明這個符號的存在,即告訴編譯器君账,這個符號是在其他文件中定義的繁堡,我這里先用著,你鏈接 的時候再到別的地方去找找看它到底是什么吧乡数。定義的時候要按C++語法完整地定義一個符號(變量或者函數(shù))椭蹄,而聲明的時候就只需要寫出這個符號的原型了。 需要注意的是瞳脓,一個符號塑娇,在整個程序中可以被聲明多次,但卻要且僅要被定義一次劫侧。試想,如果一個符號出現(xiàn)了兩種不同的定義哨啃,編譯器該聽誰的烧栋?
這 種機制給C++程序員們帶來了很多好處,同時也引出了一種編寫程序的方法拳球∩笮眨考慮一下,如果有一個很常用的函數(shù)“void f() {}”祝峻,在整個程序中的許多.cpp文件中都會被調(diào)用魔吐,那么扎筒,我們就只需要在一個文件中定義這個函數(shù),而在其他的文件中聲明這個函數(shù)就可以了酬姆。一個函數(shù)還 好對付嗜桌,聲明起來也就一句話。但是辞色,如果函數(shù)多了骨宠,比如是一大堆的數(shù)學(xué)函數(shù),有好幾百個相满,那怎么辦层亿?能保證每個程序員都可以完完全全地把所有函數(shù)的形式都 準確地記下來并寫出來嗎?

二立美、什么是頭文件

很顯然匿又,答案是不可能。但是有一個很簡單地辦法建蹄,可以幫助程序員們省去記住那么多函數(shù)原型的麻煩:我們可以把那幾百個函數(shù)的聲明語句全都先寫好琳省,放在一個文件里,等到程序員需要它們的時候躲撰,就把這些東西全部copy進他的源代碼中针贬。
這 個方法固然可行,但還是太麻煩拢蛋,而且還顯得很笨拙桦他。于是,頭文件便可以發(fā)揮它的作用了谆棱。所謂的頭文件快压,其實它的內(nèi)容跟.cpp文件中的內(nèi)容是一樣的,都是 C++的源代碼垃瞧。但頭文件不用被編譯蔫劣。我們把所有的函數(shù)聲明全部放進一個頭文件中,當某一個.cpp源文件需要它們時个从,它們就可以通過一個宏命令 “#include”包含進這個.cpp文件中脉幢,從而把它們的內(nèi)容合并到.cpp文件中去。當.cpp文件被編譯時嗦锐,這些被包含進去的.h文件的作用便發(fā) 揮了嫌松。
舉一個例子吧,假設(shè)所有的數(shù)學(xué)函數(shù)只有兩個:f1和f2奕污,那么我們把它們的定義放在math.cpp里:

/* math.cpp */
double f1()
{
    //do something here....
    return;
}
double f2(double a)
{
    //do something here...
    return a * a;
}
/* end of math.cpp */

并把“這些”函數(shù)的聲明放在一個頭文件math.h中:

/* math.h */
double f1();
double f2(double);
/* end of math.h */

在另一個文件main.cpp中萎羔,我要調(diào)用這兩個函數(shù),那么就只需要把頭文件包含進來:

/* main.cpp */
#include "math.h"
main()
{
    int number1 = f1();
    int number2 = f2(number1);
}
/* end of main.cpp */

這 樣碳默,便是一個完整的程序了贾陷。需要注意的是缘眶,.h文件不用寫在編譯器的命令之后,但它必須要在編譯器找得到的地方(比如跟main.cpp在一個目錄下)髓废。 main.cpp和math.cpp都可以分別通過編譯巷懈,生成main.o和math.o,然后再把這兩個目標文件進行鏈接瓦哎,程序就可以運行了砸喻。

三、include

#include 是一個來自C語言的宏命令蒋譬,它在編譯器進行編譯之前割岛,即在預(yù)編譯的時候就會起作用。#include的作用是把它后面所寫的那個文件的內(nèi)容犯助,完完整整地癣漆、 一字不改地包含到當前的文件中來。值得一提的是剂买,它本身是沒有其它任何作用與副功能的惠爽,它的作用就是把每一個它出現(xiàn)的地方,替換成它后面所寫的那個文件的 內(nèi)容瞬哼。簡單的文本替換婚肆,別無其他。因此坐慰,main.cpp文件中的第一句(#include "math.h")较性,在編譯之前就會被替換成math.h文件的內(nèi)容。即在編譯過程將要開始的時候结胀,main.cpp的內(nèi)容已經(jīng)發(fā)生了改變:

/* ~main.cpp */
double f1();
double f2(double);
main()
{
    int number1 = f1();
    int number2 = f2(number1);
}
/* end of ~main.cpp */

不多不少赞咙,剛剛好。同理可知糟港,如果我們除了main.cpp以外攀操,還有其他的很多.cpp文件也用到了f1和f2函數(shù)的話,那么它們也通通只需要在使用這兩個函數(shù)前寫上一句#include "math.h"就行了秸抚。

四速和、頭文件中應(yīng)該寫什么

通 過上面的討論,我們可以了解到耸别,頭文件的作用就是被其他的.cpp包含進去的健芭。它們本身并不參與編譯,但實際上秀姐,它們的內(nèi)容卻在多個.cpp文件中得到了 編譯。通過“定義只能有一次”的規(guī)則若贮,我們很容易可以得出省有,頭文件中應(yīng)該只放變量和函數(shù)的聲明痒留,而不能放它們的定義。因為一個頭文件的內(nèi)容實際上是會被引 入到多個不同的.cpp文件中的蠢沿,并且它們都會被編譯伸头。放聲明當然沒事,如果放了定義舷蟀,那么也就相當于在多個文件中出現(xiàn)了對于一個符號(變量或函數(shù))的定 義恤磷,縱然這些定義都是相同的,但對于編譯器來說野宜,這樣做不合法扫步。
所以,應(yīng)該記住的一點就是匈子,.h頭文件中河胎,只能存在變量或者函數(shù)的聲明, 而不要放定義虎敦。即游岳,只能在頭文件中寫形如:extern int a;和void f();的句子。這些才是聲明其徙。如果寫上int a;或者void f() {}這樣的句子胚迫,那么一旦這個頭文件被兩個或兩個以上的.cpp文件包含的話,編譯器會立馬報錯唾那。(關(guān)于extern访锻,前面有討論過,這里不再討論定義跟 聲明的區(qū)別了通贞。)

但是朗若,這個規(guī)則是有三個例外的。

一昌罩,頭文件中可以寫const對象的定義哭懈。因為全局的const對象默 認是沒有extern的聲明的,所以它只在當前文件中有效茎用。把這樣的對象寫進頭文件中遣总,即使它被包含到其他多個.cpp文件中,這個對象也都只在包含它的 那個文件中有效轨功,對其他文件來說是不可見的旭斥,所以便不會導(dǎo)致多重定義。同時古涧,因為這些.cpp文件中的該對象都是從一個頭文件中包含進去的垂券,這樣也就保證 了這些.cpp文件中的這個const對象的值是相同的,可謂一舉兩得羡滑。同理菇爪,static對象的定義也可以放進頭文件算芯。

二,頭文件中可 以寫內(nèi)聯(lián)函數(shù)(inline)的定義凳宙。因為inline函數(shù)是需要編譯器在遇到它的地方根據(jù)它的定義把它內(nèi)聯(lián)展開的熙揍,而并非是普通函數(shù)那樣可以先聲明再鏈 接的(內(nèi)聯(lián)函數(shù)不會鏈接),所以編譯器就需要在編譯時看到內(nèi)聯(lián)函數(shù)的完整定義才行氏涩。如果內(nèi)聯(lián)函數(shù)像普通函數(shù)一樣只能定義一次的話届囚,這事兒就難辦了。因為在 一個文件中還好是尖,我可以把內(nèi)聯(lián)函數(shù)的定義寫在最開始意系,這樣可以保證后面使用的時候都可以見到定義;但是析砸,如果我在其他的文件中還使用到了這個函數(shù)那怎么辦 呢昔字?這幾乎沒什么太好的解決辦法,因此C++規(guī)定首繁,內(nèi)聯(lián)函數(shù)可以在程序中定義多次作郭,只要內(nèi)聯(lián)函數(shù)在一個.cpp文件中只出現(xiàn)一次,并且在所有的.cpp文 件中弦疮,這個內(nèi)聯(lián)函數(shù)的定義是一樣的夹攒,就能通過編譯。那么顯然胁塞,把內(nèi)聯(lián)函數(shù)的定義放進一個頭文件中是非常明智的做法咏尝。

三,頭文件中可以寫類 (class)的定義啸罢。因為在程序中創(chuàng)建一個類的對象時编检,編譯器只有在這個類的定義完全可見的情況下,才能知道這個類的對象應(yīng)該如何布局扰才,所以允懂,關(guān)于類的 定義的要求,跟內(nèi)聯(lián)函數(shù)是基本一樣的衩匣。所以把類的定義放進頭文件蕾总,在使用到這個類的.cpp文件中去包含這個頭文件,是一個很好的做法琅捏。在這里生百,值得一提 的是,類的定義中包含著數(shù)據(jù)成員和函數(shù)成員柄延。數(shù)據(jù)成員是要等到具體的對象被創(chuàng)建時才會被定義(分配空間)蚀浆,但函數(shù)成員卻是需要在一開始就被定義的,這也就 是我們通常所說的類的實現(xiàn)。一般蜡坊,我們的做法是杠输,把類的定義放在頭文件中赎败,而把函數(shù)成員的實現(xiàn)代碼放在一個.cpp文件中秕衙。這是可以的,也是很好的辦法僵刮。 不過据忘,還有另一種辦法。那就是直接把函數(shù)成員的實現(xiàn)代碼也寫進類定義里面搞糕。在C++的類中勇吊,如果函數(shù)成員在類的定義體中被定義,那么編譯器會視這個函數(shù)為 內(nèi)聯(lián)的窍仰。因此汉规,把函數(shù)成員的定義寫進類定義體,一起放進頭文件中驹吮,是合法的针史。注意一下,如果把函數(shù)成員的定義寫在類定義的頭文件中碟狞,而沒有寫進類定義中啄枕, 這是不合法的,因為這個函數(shù)成員此時就不是內(nèi)聯(lián)的了族沃。一旦頭文件被兩個或兩個以上的.cpp文件包含频祝,這個函數(shù)成員就被重定義了。

五脆淹、頭文件中的保護措施

考 慮一下常空,如果頭文件中只包含聲明語句的話,它被同一個.cpp文件包含再多次都沒問題——因為聲明語句的出現(xiàn)是不受限制的盖溺。然而漓糙,上面討論到的頭文件中的 三個例外也是頭文件很常用的一個用處。那么咐柜,一旦一個頭文件中出現(xiàn)了上面三個例外中的任何一個兼蜈,它再被一個.cpp包含多次的話,問題就大了拙友。因為這三個 例外中的語法元素雖然“可以定義在多個源文件中”为狸,但是“在一個源文件中只能出現(xiàn)一次”。設(shè)想一下遗契,如果a.h中含有類A的定義辐棒,b.h中含有類B的定 義,由于類B的定義依賴了類A,所以b.h中也#include了a.h⊙現(xiàn)在有一個源文件泰涂,它同時用到了類A和類B,于是程序員在這個源文件中既把 a.h包含進來了辐怕,也把b.h包含進來了逼蒙。這時,問題就來了:類A的定義在這個源文件中出現(xiàn)了兩次寄疏!于是整個程序就不能通過編譯了是牢。你也許會認為這是程序 員的失誤——他應(yīng)該知道b.h包含了a.h——但事實上他不應(yīng)該知道。

使用"#define"配合條件編譯可以很好地解決這個問題陕截。在一 個頭文件中驳棱,通過#define定義一個名字,并且通過條件編譯#ifndef...#endif使得編譯器可以根據(jù)這個名字是否被定義农曲,再決定要不要繼 續(xù)編譯該頭文中后續(xù)的內(nèi)容社搅。這個方法雖然簡單,但是寫頭文件時一定記得寫進去乳规。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末形葬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子驯妄,更是在濱河造成了極大的恐慌荷并,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件青扔,死亡現(xiàn)場離奇詭異源织,居然都是意外死亡,警方通過查閱死者的電腦和手機微猖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進店門谈息,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凛剥,你說我怎么就攤上這事侠仇。” “怎么了犁珠?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵逻炊,是天一觀的道長。 經(jīng)常有香客問我犁享,道長余素,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任炊昆,我火速辦了婚禮桨吊,結(jié)果婚禮上威根,老公的妹妹穿的比我還像新娘。我一直安慰自己视乐,他們只是感情好洛搀,可當我...
    茶點故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著佑淀,像睡著了一般留美。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渣聚,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天独榴,我揣著相機與錄音,去河邊找鬼奕枝。 笑死,一個胖子當著我的面吹牛瓶堕,可吹牛的內(nèi)容都是我干的隘道。 我是一名探鬼主播,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼郎笆,長吁一口氣:“原來是場噩夢啊……” “哼谭梗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起宛蚓,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤激捏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后凄吏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體远舅,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年痕钢,在試婚紗的時候發(fā)現(xiàn)自己被綠了图柏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡任连,死狀恐怖蚤吹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情随抠,我是刑警寧澤裁着,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站拱她,受9級特大地震影響二驰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜椭懊,卻給世界環(huán)境...
    茶點故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一诸蚕、第九天 我趴在偏房一處隱蔽的房頂上張望步势。 院中可真熱鬧,春花似錦背犯、人聲如沸坏瘩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽倔矾。三九已至,卻和暖如春柱锹,著一層夾襖步出監(jiān)牢的瞬間哪自,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工禁熏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留壤巷,地道東北人。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓瞧毙,卻偏偏與公主長得像胧华,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宙彪,可洞房花燭夜當晚...
    茶點故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 概述:聲明是將一個名稱引入一個程序.定義提供了一個實體在程序中的唯一描述.聲明在單個作用域內(nèi)可以重復(fù)多次(類成員除...
    抓兔子的貓閱讀 625評論 0 3
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,523評論 1 51
  • 1 原理 1.1 首先矩动,關(guān)于聲明和定義的區(qū)別。 這種寫法(函數(shù)原型后加;號表示結(jié)束的寫法)只能叫函數(shù)聲明而不能叫函...
    Pitfalls閱讀 6,513評論 2 12
  • 1.面向?qū)ο蟮某绦蛟O(shè)計思想是什么? 答:把數(shù)據(jù)結(jié)構(gòu)和對數(shù)據(jù)結(jié)構(gòu)進行操作的方法封裝形成一個個的對象。 2.什么是類畜挥?...
    少帥yangjie閱讀 5,011評論 0 14
  • 1.項目經(jīng)驗 2.基礎(chǔ)問題 3.指南認識 4.解決思路 ios開發(fā)三大塊: 1.Oc基礎(chǔ) 2.CocoaTouch...
    陽光的大男孩兒閱讀 4,999評論 0 13