【C語言精華】頭文件組織與包含原則!純乾貨,這是一篇有價值的文章!

本文假定讀者已具有基本的C編譯知識。如非特殊說明,文中「源文件」指 * .c文件,「頭文件」指 *.h文件,「引用」指包含頭文件。前端

1、頭文件做用

C語言裏,每一個源文件是一個模塊,頭文件爲使用該模塊的用戶提供接口。接口指一個功能模塊暴露給其餘模塊用以訪問具體功能的方法。程序員

使用源文件實現模塊的功能,使用頭文件暴露單元的接口。用戶只需包含相應的頭文件就可以使用該頭文件中暴露的接口。編程

經過頭文件包含的方法將程序中的各功能模塊聯繫起來有利於模塊化程序設計:windows

        1)經過頭文件調用庫功能。在不少場合,源代碼不便(或不許)向用戶公佈,只要向用戶提供頭文件和二進制庫便可。用戶只需按照頭文件中的接口聲明來調用庫功能,而沒必要關心接口如何實現。編譯器會從庫中提取相應的代碼。數組

        2)頭文件能增強類型安全檢查。若某個接口的實現或使用方式與頭文件中的聲明不一致,編譯器就會指出錯誤。這一簡單的規則能大大減輕程序員調試、改錯的負擔。安全

在預處理階段,編譯器將源文件包含的頭文件內容複製到包含語句(#include)處。在源文件編譯時,連同被包含進來的頭文件內容一塊兒編譯,生成目標文件(.obj)。模塊化

若是所包含的頭文件很是龐大,則會嚴重下降編譯速度(使用GCC的-E選項可得到並查看最終預處理完的文件)。所以,在源文件中應僅包含必需的頭文件,且儘可能不要在頭文件中包含其它頭文件。函數

 

2、 頭文件組織原則

源文件中實現變量、函數的定義,並指定連接範圍。頭文件中書寫外部須要使用的全局變量、函數聲明及數據類型和宏的定義。佈局

建議組織頭文件內容時遵循如下原則:單元測試

    1)頭文件劃分原則:類型定義、宏定義儘可能與函數聲明相分離,分別位於不一樣的頭文件中。內部函數聲明頭文件與外部函數聲明頭文件相分離,內部類型定義頭文件與外部類型定義頭文件相分離。

注意,類型和宏定義有時沒法分拆爲不一樣文件,好比結構體內數組成員的元素個數用常量宏表示時。所以僅分離類型宏定義與函數聲明,且分別置於*.th和*.fh文件(並不是強制要求)。

    2)頭文件的語義層次化原則:頭文件須要有語義層次。不一樣語義層次的類型定義不要放在一個頭文件中,不一樣層次的函數聲明不要放在一個頭文件中。

    3)頭文件的語義相關性原則:同一頭文件中出現的類型定義、函數聲明應該是語義相關的、有內部邏輯關係的,避免將無關的定義和聲明放在一個頭文件中。

    4)頭文件名應儘可能與實現功能的源文件相同,即module.c和module.h。但源文件不必定要包含其同名的頭文件。

    5)頭文件中不該包含本地數據,以下降模塊間耦合度。

即只有源文件本身使用的類型、宏定義和變量、函數聲明,不該出如今頭文件裏。做用域限於單文件的私有變量和函數應聲明爲static,以防止外部調用。將私有類型置於源文件中,會提升聚合度,並減小沒必要要的格式外漏。

    6)頭文件內不容許定義變量和函數,只能有宏、類型(typedef/struct/union/enum等)及變量和函數的聲明。

特殊狀況下可extern基本類型的全局變量,源文件經過包含該頭文件訪問全局變量。但頭文件內不該extern自定義類型(如結構體)的全局變量,不然將迫使本不須要訪問該變量的源文件包含自定義類型所在頭文件[1]。

    7)說明性頭文件不須要有對應的源文件。此類頭文件內大多包含大量概念性宏定義或枚舉類型定義,不包含任何其餘類型定義和變量或函數聲明。此類頭文件也不該包含任何其餘頭文件。

    8)使用#pragma once或header guard(亦稱include guard或macro guard)避免頭文件重複包含。#pragma once是一種非標準但已被現代編譯器普遍支持的技巧,它明確告知預處理器「不要重複包含當前頭文件」。而header guard則經過預處理命令模擬相似行爲:


 

使用#pragma once相比header guard具備兩個優勢:

        ① 更快。編譯器不會第二次讀取標記#pragma once的文件,但卻會讀若干遍使用header guard 的文件(尋找#endif);

        ② 更簡單。再也不須要爲每一個文件的header guard取名,避免宏名重名引起的「找不到聲明」問題。

缺點則是:

#pragma once保證物理上的同一個文件不會被包含屢次,沒法對頭文件中的一段代碼做#pragma once聲明。若某個頭文件具備多份拷貝(內容相同的多個文件),pragma不能保證它們不被重複包含。固然,這種重複包含很容易被發現並修正。

    9) C++中要引用C函數時,函數所在頭文件內應包含extern "C"。


 

被extern "C"修飾的變量和函數將按照C語言方式編譯和鏈接,不然編譯器將沒法找到C函數定義,從而致使連接失敗。

    10)頭文件內要有面向用戶的充足註釋,從應用角度描述接口暴露的內容。

 

3、 頭文件包含原則

在實際編程中,經常因頭文件包含不當而引起編譯時報告符號未定義的錯誤或重複定義的警告。

要消除符號未定義的編譯錯誤,只需在引用符號(變量、函數、數據類型及宏等)前確保它已被聲明或定義[4]。要消除重複定義的警告,則需合理設計頭文件包含順序和層次。

建議包含頭文件時遵循如下原則:

1)源文件內的頭文件包含順序應從最特殊到通常,

如:


 

優勢是每一個頭文件必須include須要的關聯頭文件,不然會報錯。

同時,源文件同名頭文件置於包含列表前端便於檢查該頭文件是否自完備,以及類型或函數聲明是否與標準庫衝突。

2)減小頭文件的嵌套和交叉引用,頭文件僅包含其真正須要顯式包含的頭文件。

例如,頭文件A中出現的類型定義在頭文件B中,則頭文件A應包含頭文件B,除此之外的其餘頭文件不容許包含。

頭文件的嵌套和交叉引用會使程序組織結構和文件組織變得混亂,同時形成潛在的錯誤。大型工程中,原有頭文件可能會被多個其餘(源或頭)文件包含,在原有頭文件中添加新的頭文件每每牽一髮而動全身。若頭文件中類型定義須要其餘頭文件時,可將其提出來單獨造成一個全局頭文件。

3)頭文件應包含哪些頭文件僅取決於自身,而非包含該頭文件的源文件。

例如,編譯源文件時須要用到頭文件B,且源文件已包含頭文件A,而索性將頭文件B包含在頭文件A中,這是錯誤的作法。

4)儘可能保證用戶使用此頭文件時,無需手動包含其餘前提頭文件,即此頭文件內已包含前提頭文件。

例如,面積相關操做的頭文件Area.h內已包含關於點操做的頭文件Point.h,則用戶包含Area.h後無需再手動包含Point.h。這樣用戶就沒必要了解頭文件的內在依賴關係。

5)頭文件應是自完備的,即在任一源文件中包含任一頭文件而不會產生編譯錯誤。

6)源文件中包含的頭文件儘可能不要有順序依賴。

7)儘可能在源文件中包含頭文件,而非在頭文件中。且源文件僅包含所需的頭文件。

8)頭文件中若能前置聲明(亦稱前向聲明[5]),就不要包含另外一頭文件。僅當前置聲明不能知足或過於麻煩時才使用include,如此可減小依賴性方面的問題。

示例以下:


 

如上,在OmciChkFunc函數的實現源文件內包含T_MeInfoMap和T_OmciMsg所在頭文件便可。

另舉一例以下:


 

如上,CompareRecFunc函數原型由其餘頭文件提供,此處爲避免頭文件交叉引用定義其異名同構原型CmpRecFunc。

在不會引發歧義的前提下,頭文件內儘量使用VOID指針代替非基本類型的值變量或指針,以免再包含類型定義所在的頭文件。但這將影響代碼可讀性並下降程序執行效率,應權衡利弊。

9)避免包含重量級的平臺頭文件,如windows.h或d3d9.h等。若僅使用該頭文件少許函數,可extern函數到源文件內。以下:


 

若還使用該頭文件某些類型和宏定義,可建立適配性源文件。在該源文件內包含平臺頭文件,封裝新的接口並將其聲明在同名頭文件內,其餘源文件將經過適配頭文件間接訪問平臺接口。以下:


 

    10)對於函數庫(包括標準庫和自定義的公共宏及接口)的頭文件,可將其加入到一個通用頭文件中。須要控制該頭文件的體積(主要是該頭文件所包含的全部頭文件內容大小),並確保全部源文件首先包含該通用頭文件。示例以下:


 

注意,示例頭文件內包含C庫文件雖能簡化包含,但卻與規則1衝突。也可另外增長包含庫文件列表的通用頭文件。

    11)若不肯定類型、宏定義或函數聲明所在頭文件具體路徑,可在源文件中再次定義或聲明,編譯器會以redefined警告或conflicting錯誤給出類型、宏定義或函數聲明所在頭文件路徑。

 

4、代碼文件組織原則

建議C語言項目中代碼文件組織遵循如下原則:

    1)使用層次化和模塊化的軟件開發模型。每一個模塊只能使用所在層和下一層模塊提供的接口。

    2)每一個模塊的文件(可能多個)保存在一個獨立文件夾中。

模塊文件較多時可採用子目錄的方式,物理上隔離不一樣層次的文件。子目錄下源文件和頭文件應分開存放,如分別置入include和source目錄。

    3)用於模塊裁減的條件編譯宏保存在一個獨立文件中,便於軟件裁減。

    4)硬件相關代碼和操做系統相關代碼與工程代碼相對獨立保存,以便於軟件移植。

    5)按相同功能或相關性組織源文件和頭文件。同一文件內的聚合度要高,不一樣文件中的耦合度要低。

在對既有工程作單元測試時,耦合度低的文件佈局很是便於搭建環境。

    6)聲明和定義分開,使用頭文件暴露模塊須要提供給外部的類型、宏、變量和函數。儘可能作到模塊對外部透明,用戶在使用模塊功能時無需瞭解具體的實現。

    7)做爲對外接口的頭文件一經發布,應保持穩定。修改時必定要慎重。

    8)文件夾和文件命名要可以反映出模塊的功能。

    9)正式版本和測試版本使用統一文件,使用宏控制是否產生測試輸出。

    10)必要的註釋不可缺乏。


 

最後,無論你是轉行也好,初學也罷,進階也可,若是你想學編程~

【值得關注】個人 C/C++編程學習交流俱樂部!【點擊進入】

問題答疑,學習交流,技術探討,還有超多編程資源大全,零基礎的視頻也超棒~

相關文章
相關標籤/搜索