2010-07-14 69 views
93

我一直看到有人寫爲什麼在.h文件中使用#ifndef CLASS_H和#define CLASS_H,但不在.cpp中?

class.h

#ifndef CLASS_H 
#define CLASS_H 

//blah blah blah 

#endif 

問題是,他們爲什麼不也這樣做,對於含有類功能定義.cpp文件?

比方說,我有main.cppmain.cpp包括class.hclass.h文件不會導入任何內容,那麼main.cpp如何知道class.cpp中的內容?

+5

「導入」可能不是你想在這裏使用的詞。包括。 – 2010-07-14 13:59:23

+4

在C++中,文件和類之間沒有1對1的關聯。你可以根據需要將多個類放入一個文件中(甚至可以將一個類分成幾個文件,儘管這很少有用)。因此宏應該是'FILE_H',而不是'CLASS_H'。 – sbi 2010-07-14 14:12:03

+1

看我的[包括警衛建議](http://stackoverflow.com/questions/1744144/include-guards/1744302#1744302)。 – 2010-07-15 04:13:59

回答

213

首先,以解決您的第一次調查:

當您在.H文件中看到這一點:

#ifndef FILE_H 
#define FILE_H 

/* ... Declarations etc here ... */ 

#endif 

這被包括防止頭文件從的預處理技術多次,由於各種原因,這可能是有問題的。在編譯項目期間,每個文件(通常)都會被編譯。簡單來說,這意味着編譯器將採取您的。cpp文件,打開文件#included,將它們全部拼接成一個海量文本文件,然後進行語法分析,最後將其轉換爲中間代碼,優化/執行其他任務,最後生成彙編輸出目標架構。因此,如果一個文件在.cpp文件下多次執行#included,編譯器會將其文件內容追加兩次,因此如果該文件中有定義,將會收到編譯器錯誤,告訴您重新定義了一個變量。當文件由編譯過程中的預處理器步驟處理時,第一次到達其內容時,前兩行將檢查是否已爲預處理器定義了FILE_H。如果沒有,它將定義FILE_H並繼續處理它和#endif指令之間的代碼。預處理器下次看到該文件的內容時,對FILE_H的檢查將爲false,因此它將立即掃描到#endif並在其後繼續。這可以防止重新定義錯誤。

,並解決你的第二個問題:

在C++編程,因爲我們分開發展成兩個文件類型通用做法。其中一個擴展名爲.h,我們稱之爲「頭文件」。它們通常提供函數,類,結構體,全局變量,類型定義,預處理宏和定義等的聲明。基本上,它們只是爲您提供有關代碼的信息。然後我們有擴展名爲.cpp,我們稱之爲「代碼文件」。這將爲這些函數,類成員,需要定義的任何結構成員,全局變量等提供定義。因此,.h文件聲明代碼,並且.cpp文件實現該聲明。由於這個原因,我們一般在編譯期間將每個文件編譯成一個對象,然後鏈接這些對象(因爲您幾乎從不會看到一個文件包含另一個.cpp文件)。

如何解決這些外部問題是鏈接器的工作。當您的編譯器處理main.cpp時,它將通過包括class.h獲取class.cpp中的代碼的聲明。它只需要知道這些函數或變量是什麼樣的(這是聲明給你的)。所以它將你的文件編譯成一個目標文件(稱爲main.obj)。同樣,class.cpp被編譯成class.obj文件。爲了生成最終的可執行文件,調用鏈接器將這兩個目標文件鏈接在一起。對於任何未解析的外部變量或函數,編譯器會在發生訪問的地方放置一個存根。然後鏈接程序將取出該存根,並在另一個列出的目標文件中查找代碼或變量,如果找到它,它將來自兩個目標文件的代碼合併到一個輸出文件中,並將該存根替換爲該函數的最終位置,或者變量。這樣,main.cpp中的代碼可以調用函數並使用class.cpp中的變量如果僅在他們聲明瞭class.h

我希望這是有幫助的。

+15

+1 - 感謝您花時間作出全面的回答。 – 2014-03-14 03:43:48

+0

我在過去幾天試圖理解.h和.cpp。這個答案節省了我的時間和興趣來學習C++。寫得好。謝謝Justin! – 2016-08-03 10:55:34

+0

你真的解釋得很好!如果是圖像,答案可能會相當不錯 – alamin 2017-06-09 21:34:56

3

這是聲明和定義之間的區別。頭文件通常只包含聲明,源文件包含定義。

爲了使用某些東西,您只需要知道它的聲明,而不是定義。只有鏈接器需要知道定義。

所以這就是爲什麼你會在一個或多個源文件中包含一個頭文件,但是你不會在另一個源文件中包含源文件。您的意思是#include,而不是導入。

10

CLASS_Hinclude guard;它被用來避免相同的頭文件被多次(通過不同的路由)包含在同一個CPP文件中(或者更準確地說,是相同的translation unit),這將導致多重定義錯誤。

在CPP文件中不需要包含保護,因爲根據定義,只能讀取一次CPP文件的內容。

您似乎已經將包含的警衛解釋爲具有與其他語言(如Java)中的import語句相同的功能;但是,情況並非如此。 #include本身大致相當於其他語言的import

+2

「在同一個CPP文件中」應該是「在同一翻譯單元內」。 – dreamlax 2010-07-14 14:00:32

+0

@dreamlax:好點 - 這正是我最初寫的內容,但後來我發現一個不懂的人包括警衛只會被「翻譯部門」這個詞所困惑。我將編輯答案,在括號中添加「翻譯單元」 - 這應該是兩個世界中最好的。 – 2010-07-14 14:04:37

2

main.cpp不必知道什麼是class.cpp。它只需知道它使用的函數/類的聲明,並且這些聲明在class.h中。

的地方之間的連接體,其中連接在class.h聲明的函數/類用於和它們在class.cpp實現

0

.cpp文件不包含(使用#include)轉換成其他文件。因此他們不需要包括守衛。 Main.cpp只會在class.h中知道您在class.cpp中實施的類的名稱和簽名 - 這是頭文件的用途。 (您需要確保class.h準確地描述了您在class.cpp中實施的代碼。)由於鏈接程序的努力,class.cpp中的可執行代碼將在main.cpp中提供給可執行代碼。

0

由於Headerfiles定義了類包含的成員(數據結構)和cpp文件實現它。

當然,主要原因是您可以在其他.h文件中多次包含一個.h文件,但這會導致多個類的定義,這是無效的。

2

這是爲頭文件完成的,以便內容僅在每個預處理源文件中出現一次,即使它包含多次(通常是因爲它包含在其他頭文件中)。當它第一次被包含時,符號CLASS_H(被稱爲包括後衛)尚未定義,因此文件的所有內容都包含在內。這樣做會定義該符號,因此如果再次包含該符號,則會跳過該文件的內容(在#ifndef/#endif塊內)。

因爲(通常)沒有任何其他文件包含,所以不需要爲源文件本身執行此操作。

對於您的最後一個問題,class.h應該包含類的定義,以及所有成員,關聯函數和其他任何的聲明,以便包含它的任何文件都有足夠的信息來使用該類。這些函數的實現可以放在單獨的源文件中;你只需要聲明來調用它們。

5

它沒有 - 至少在編譯階段。

C++程序從源代碼轉換爲機器代碼翻譯分三個階段進行:

  1. 預處理 - 的預處理器解析爲以#開始的行中的所有源代碼,並執行該指令。在你的情況下,你的文件class.h的內容被插入到行#include "class.h的位置。由於您可能會在多個位置包含頭文件,因此#ifndef子句可以避免重複的聲明錯誤,因爲預處理程序指令僅在首次包含頭文件時纔會定義。
  2. 編譯 - 編譯器現在將所有預處理的源代碼文件轉換爲二進制對象文件。
  3. 鏈接 - 鏈接器鏈接(因此名稱)在一起的目標文件。對類或其方法之一(應該在class.h中聲明並在class.cpp中定義)的引用將解析爲其中一個對象文件中的相應偏移量。我寫'你的一個目標文件',因爲你的類沒有需要被定義在一個名爲class.cpp的文件中,它可能在一個鏈接到你的項目的庫中。

總之,聲明可以通過頭文件共享,而聲明到定義的映射由鏈接器完成。

0

通常預期代碼模塊(如.cpp文件)被編譯一次並鏈接到多個項目中,以避免不必要的重複編譯邏輯。例如,g++ -o class.cpp將生成class.o,然後您可以從多個項目鏈接到使用g++ main.cpp class.o

我們可以使用#include作爲我們的鏈接器,正如您似乎暗示的那樣,但是如果我們知道如何使用我們的編譯器正確鏈接,而且鍵盤輸出更少,編譯浪費更少,而不是我們的代碼更多的按鍵和更浪費的重複編譯...

但是,仍然需要將頭文件包含到多個項目中的每一箇中,因爲它爲每個模塊提供了接口。沒有這些頭文件,編譯器就不會知道.o文件引入的任何符號。

重要的是要認識到頭文件是介紹這些模塊符號的定義;一旦意識到這一點,多重包含可能導致重新定義符號(這會導致錯誤)是有道理的,所以我們使用包含防護來防止這種重新定義。

相關問題