簡體   English   中英

.h 和 .cpp 文件之間的真正區別是什么?

[英]What's the REAL difference between .h and .cpp files?

這個問題在 StackOverflow 上多次發布,但大多數答案都表示類似於“.h 文件應該包含聲明,而 .cpp 文件應該包含它們的定義/實現”。 我注意到在 .h 文件中簡單地定義函數工作得很好。 在 .h 文件中聲明函數但在 .cpp 文件中定義和實現它們的目的是什么? 它真的減少了編譯時間嗎? 還有什么?

實際上:圍繞 .h 文件的約定已經到位,因此您可以安全地將該文件包含在項目中的多個其他文件中。 頭文件旨在共享,而代碼文件則不是。

讓我們以您定義函數或變量的示例為例。 假設您的頭文件包含以下行:

標題.h:

int x = 10;

代碼.cpp:

#include "header.h"

現在,如果您只有一個代碼文件和一個頭文件,這可能會正常工作:

g++ code.cpp -o outputFile

但是,如果您有兩個代碼文件,則會中斷:

標題.h:

int x = 10;

代碼1.cpp:

#include "header.h"

代碼2.cpp:

#include "header.h"

進而:

g++ code1.cpp -c  (produces code1.o)
g++ code2.cpp -c  (produces code2.o)
g++ code1.o code2.o -o outputFile

這會中斷,特別是在鏈接器步驟,因為現在您在同一個可執行文件中有兩個具有相同符號的符號,而鏈接器不知道它應該用它做什么。 當你在 code1 中包含你的標題時,你會得到一個符號“x”,當你在 code2 中包含你的標題時,你會得到另一個符號“x”。 鏈接器不知道您在這里的意圖,因此會引發錯誤:

code2.o:(.data+0x0): multiple definition of `x'
code1.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

這再次只是鏈接器說它無法解決您現在在同一個可執行文件中有兩個同名符號的事實。

.h 和 .cpp 文件之間的真正區別是什么?

它們基本上都只是文本文件。 從某種角度來看,它們唯一的區別是文件名。

但是,許多與編程相關的工具會根據文件的名稱對文件進行不同的處理。 例如,有些工具會檢測編程語言:.c 編譯為 C 語言,.cpp 編譯為 C++,.h 根本不編譯。

對於頭文件,名稱對編譯器來說根本無關緊要。 名稱可以是 .h 或 .header 或其他任何名稱,它不會影響預處理器如何包含它。 然而,遵守共同約定以避免混淆是一種很好的做法。

我注意到在 .h 文件中簡單地定義函數工作得很好。

函數是否聲明為非內聯的? 您是否曾經將頭文件包含在多個翻譯單元中? 如果您對兩者的回答都是肯定的,那么您的程序已經形成錯誤。 如果你沒有,那么這就解釋了為什么你沒有遇到任何問題。

它真的減少了編譯時間嗎?

是的。 與編譯更大的翻譯單元相比,將函數定義分成更小的翻譯單元確實可以減少編譯所述翻譯單元的時間。

這是因為做更少的工作需要更少的時間。 需要注意的是,當只修改一個翻譯單元時,不需要重新編譯其他翻譯單元。 如果您只有一個翻譯單元,那么您必須編譯它,即整個程序。

多個翻譯單元也更好,因為它們可以並行編譯,這允許利用現代多核硬件。

還有什么?

需要什么嗎? 不必等待幾分鍾來編譯你的程序,而不是一天,這大大提高了開發速度。

關於文件的組織,還有其他一些優點。 特別是,能夠為不同的目標系統為相同的功能定義不同的實現以便能夠支持多個平台是非常方便的。 對於頭文件,您必須使用宏進行技巧,而對於源文件,您只需選擇要編譯的文件即可。

在頭文件中實現函數不是一種選擇的另一個用例是分發沒有源的庫,就像一些中間件提供者所做的那樣。 您必須提供標頭,否則無法調用您的函數,但如果您的所有源代碼都在標頭中,那么您就已經放棄了商業機密。 編譯后的源代碼至少必須經過逆向工程。

請記住,就文件處理而言,C++ 編譯器是一個相當簡單的野獸。 它允許做的只是讀取單個源代碼文件(並且,通過預處理器,邏輯地將文件#include s 的任何文件的內容插入到傳入的文本流中,遞歸),解析內容,並吐出生成的.o文件。

對於小程序,將整個代碼庫保存在單個.cpp文件(甚至單個.h文件)中效果很好,因為編譯器需要加載到內存中的代碼行數很少(相對於計算機的 RAM)。

但是想象一下你正在開發一個有數千萬行代碼的怪物程序——是的,這樣的事情確實存在。 一次將那么多代碼加載到 RAM 中可能會對除最強大計算機之外的所有計算機的能力造成壓力,導致編譯時間過長甚至徹底失敗。

更糟糕的是,接觸.h文件中的任何代碼都需要直接或間接重新編譯#include.h文件的任何其他文件——因此,如果您的所有代碼都在.h文件中,那么您的編譯器可能會花費大量時間不必要地重新編譯大量實際上並未更改的代碼。

為了避免這些問題,C++ 允許您將代碼放入多個.cpp文件中。 由於.cpp文件(至少在傳統上)從不#include任何東西,因此您的 Makefile 或 IDE 需要重新編譯任何給定的.cpp文件的唯一時間是在您實際修改該確切文件或.h文件之后它#include的。

因此,當您修改了程序中 700 個.cpp文件中的第 375 個.cpp文件中的一個函數,現在您想測試您的修改時,編譯器只需重新​​編譯該.cpp文件,然后重新鏈接.o文件轉換為可執行文件。 如果 OTOH 您修改了.h文件,編譯可能會更長,因為現在構建系統必須直接或間接重新編譯包含該.h文件的所有其他文件,以防萬一您更改了某些內容的含義文件依賴。

.cpp 文件還使鏈接時問題更容易處理。 例如,如果您想要一個全局變量,在 .cpp 文件中定義該全局變量(並且可能在 .h 文件中為其聲明一個extern )很簡單; 如果 OTOH 您想在 .h 文件中執行此操作,則必須非常小心,否則您的鏈接器最終會出現重復符號錯誤,和/或會再次出現對 One Definition Rule 的微妙違反以后咬你。

真正的區別在於您的編程環境分別列出了 .h 和 .cpp 文件。 和/或適當地填充文件瀏覽器對話框。 和/或嘗試將 .cpp 文件編譯為對象形式(但不對 .h 文件執行此操作)。 無論如何,這取決於您使用的 IDE/環境。

第二個區別是人們假定您的 .h 文件是頭文件,而您的 .cpp 文件是代碼源文件。

如果你不關心人或開發環境,你可以把你想要的任何該死的東西放在一個 .h 或 .cpp 文件中,並給他們任何你想要的名字。 您可以將聲明放在 .cpp 文件中並將其稱為“包含文件”,將定義放在 .pas 文件中並將其稱為“源文件”。

在受限環境中工作時,我必須做這種事情

頭文件不是 c 的原始定義的一部分。 沒有他們,世界就很好。 打開和關閉大量頭文件確實減慢了 c 的編譯速度,這就是我們獲得預編譯頭文件的原因。 預編譯的頭文件確實加快了源代碼的編譯和鏈接,但並不比編寫匯編程序、機器代碼或任何其他不利用其他人合作設計的東西快環境

將聲明放在頭文件中,將定義放在代碼源文件中是很有用的 這就是為什么你應該這樣做。 沒有要求

每當您看到#include <header.h>指令時,就假設header.h的內容正在被復制並粘貼到#include指令出現的位置。

.cpp文件被編譯為.obj文件。 它們不知道任何其他.cpp文件的存在,並且是單獨編譯的。 這就是為什么我們需要在使用它們之前聲明它們 - 否則編譯器將不知道我們試圖調用的函數是否存在於不同的.cpp文件中。

我們使用頭文件在多個.cpp文件之間共享聲明,以避免必須為每個.cpp文件一遍又一遍地編寫相同的代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM