2015-10-20 51 views
3

我有自定義堆棧實現的例子。據我瞭解,.h文件應該只包含聲明cpp文件應該只包含實現。我在cplusplus.com/stack_example上找到了一個自定義堆棧的例子,它看起來像下面這樣。什麼是頭文件和C++中的實現文件?

Stack.h文件

#ifndef _STACK_H_ 
#define _STACK_H_ 

#include <iostream> 
#include "Exception.h" 

template <class T> 
class Stack { 
    public: 
     Stack():top(0) { 
      std::cout << "In Stack constructor" << std::endl; 
     } 
     ~Stack() { 
      std::cout << "In Stack destructor" << std::endl; 
      while (!isEmpty()) { 
       pop(); 
      } 
      isEmpty(); 
     } 

     void push (const T& object); 
     T pop(); 
     const T& topElement(); 
     bool isEmpty(); 

    private: 
     struct StackNode {    // linked list node 
      T data;      // data at this node 
      StackNode *next;   // next node in list 

      // StackNode constructor initializes both fields 
      StackNode(const T& newData, StackNode *nextNode) 
       : data(newData), next(nextNode) {} 
     }; 

     // My Stack should not allow copy of entire stack 
     Stack(const Stack& lhs) {} 

     // My Stack should not allow assignment of one stack to another 
     Stack& operator=(const Stack& rhs) {} 
     StackNode *top;     // top of stack 

}; 

現在我有問題。這個.h文件顯然揭示了一些實現細節。構造函數和析構函數都在.h文件中執行執行。根據我的理解,這些應該在.cpp文件中執行。此外,還有struct StackNode這也是.h文件中實現。這甚至有可能在.cpp文件中實現,只能在頭文件中聲明它?作爲一般規則,如果這些文件位於.cpp實施文件中,這會不會更好?編碼這個東西的最好方法是什麼,以便遵循C++規則?

+5

你把事情需要由多個[翻譯單元]可以使用頭文件(http://en.wikipedia.org/wiki/Translation_unit)。如果你想隱藏實現細節,可以考慮使用[the pimpl idiom](http://c2.com/cgi/wiki?PimplIdiom)或其他不透明的結構指針。 –

回答

3

有什麼必須是在頭文件中,除了標準庫頭再沒標準規則。我的意思是,理論上講,我們可以完全避免頭文件和.cpp文件中的複製和粘貼聲明。

這就是說,這更多的是一個常識和經驗的問題。你會根據你的意願和你的需要把東西放入標題或.cpp。我們可以列出一些使用案例:

  • 模板(如在你的例子)聲明實現通常去頭。有關更多信息,請參閱this thread

  • 將函數的聲明在一個頭,當你想/想他們會在使用超過一個翻譯單元

  • 當你別不將函數的聲明在頭如果您希望函數爲inline d並且希望該函數在不同的翻譯單元中使用,那麼您將它的定義放在一個標題中(前面有如果我是inline關鍵字T的一個非成員函數)

  • 把一個類聲明(又名向前聲明)的頭,當你想/想要的那個類型的對象將是訪問 *從不同的翻譯單位

  • 把類的聲明,當你想/只想要一個翻譯單元將要訪問該類的水箱內

  • do把類定義(即全班接口)中,當你認爲/頭想要的那個類型的對象將是*在不同的翻譯單位

    創建
  • 把一個類定義在頭,當你想要的該類型的對象將只在一個翻譯單元創建

    如果你想定義一個全局變量,你很難把定義在一個頭文件(除非你想包括頭只是一個單一的翻譯
  • 單位)

  • 如果你正在開發一個圖書館,你就會把這些函數和類聲明要爲用戶提供進入頭文件

  • 如果你正在開發一個圖書館,你會找到一種方法,投入.cpp文件的實現細節你不想讓公衆(如@Joachim Pileborg建議,見the pimpl idiom

  • 你平時想用用指令在頭部聲明或,因爲他們會污染那些翻譯單位將#include

  • 當它是可能的,你希望其他#include頭到你的;你肯定喜歡轉發聲明,你需要使你的程序編譯什麼

  • 最後,粗略地講,你把水箱內的少的東西,更快的文件進行編譯;並且讓我明確地指出,你想讓你的文件快速編譯!


注意

上面我已經說過主要的類和函數,但一般大拇指這些規則是有效的枚舉和typedef聲明。

在頭從列表中缺少成員函數的定義,因爲它裏面類定義函數定義的問題,而不是.h VS .cpp文件

*隨着訪問我的意思是通過指針或參考使用,相對於創建的

+0

這是一個很有前途的列表,但是(當前)省略了最有趣的例子之一 - 類/結構定義。 –

+0

@TonyD這是最糟糕的,我在類定義*和聲明*之間混淆,所以答案是誤導。我試圖澄清這一點。請通過編輯或添加更多的內容免費改進當前列表。 –

4

頭文件與源文件沒有根本的區別。

原則上,標頭可以包含與源文件相同的代碼結構。按照慣例,唯一的區別是頭文件和源文件的區別在於頭文件應該是其他文件的文件,我們通常不會使用源文件(儘管我們可以,如果我們覺得冒險的話)。

現在重要的是如果一個文件通過多個源文件得到#include d會發生什麼情況。請注意,這基本上等同於將相同的代碼複製粘貼到多個.cpp文件。在這種情況下,某些事情可能會讓你陷入麻煩。

特別是,如果最終在兩個不同的源文件中出現兩個相同符號的定義,那麼您的程序格式不正確(根據the one-definition rule),鏈接器可能會拒絕鏈接您的程序。

對於不同的源文件,這通常不是問題,除非你在不同的上下文中意外地重用了一個名稱。一旦頭文件進入圖片(這只是複製粘貼的代碼),事情開始看起來不同。您仍然可以在頭文件中定義一個定義,但是如果該頭文件被多個源文件拉入,您自己會違背單定義規則。

因此,約定是隻將東西放入頭文件中,可安全地跨多個源文件進行復制。這包括聲明,內聯函數定義和模板。

這裏的關鍵實現可能是頭文件是一個相當古老的工具,用於在源文件之間共享代碼。因此,他們嚴重依賴於用戶足夠聰明,不要搞砸了。

3

究竟是什麼進入頭文件?

與您學習的內容和您的設計無關,請將盡可能少的內容放入標題中。標題是使用實現細節所需的最少量東西。與大頭文件相比,簡單頭文件通常也更容易使用代碼,並且其包含的編譯時間更短。

當然,C++頭文件與源文件沒有區別,它是根據您的技能將源代碼編寫到不同的文件中以使您的項目更易於使用和理解。

有些情況下你不得不把東西放在標題中(例如使用模板),如果你不這樣做,最好把東西放入標題&源文件中。

編譯器將轉換爲二進制ANY源文件(header或cpp),唯一的必要條件是至少有一些「可編譯」代碼(函數/方法的主體,即使爲空)。

一個誰做的區別是程序員:

如果你把一個頭的功能不能被編譯的簽名,你也來編譯函數體,以實際上使得它可運行的程序(如果你沒有身體的某個地方,你會得到鏈接錯誤)

Header.h

#pragma once 

// by including this file you are actually promising that 
// you will later add also the body of this function. 
int function(int a); 

Source.cpp

#include <liba> 
#include <libb> 
#include "Header.h" 

// by compiling this file AND linking into your binaries 
// you satisfy the promise by providing the real function 
int function(int a){ 
    return liba::fun(a)+libb::fun(b); 
} 

是不是有用?是。你所包含的每個文件都會增加編譯時間併爲你的代碼添加依賴(如果你改變了一個頭文件,你必須重新編譯你的程序,這樣你也可能會遇到編譯錯誤,但是如果你把東西分成兩個文件,你可以有更簡單的頭文件和你可以在你的函數內改變一些小東西,而不必重新編譯大量文件:在大型項目中編譯時間是一個真正的問題)。

以下的2個例子是等價的(產生相同組件):

的main.cpp

#include "Header.h" 
int main(){ 

    return function(3); 
} 

你僅包括1個文件


MAIN2。 cpp

#include "Source.cpp" //note source.cpp here! 
int main(){ 

    return function(3); 
} 

要包括Source.cppHeader.hlibalibb(甚至更多)的文件,用結果4X慢編譯時間。


當然頭還可以讓你通過避免重複的定義有一個更簡單的生活編程。

雙列入

#include "Header.h" 
#include "Header.h" //bad, but allowed! 
int main(){ 

    return function(3); 
} 

雙列入

#include "Source.cpp" 
#include "Source.cpp" //error! redefinition (not compile) 
int main(){ 

    return function(3); 
} 

來自不同地方的雙重涵

file1.cpp

#include "Source.cpp" // Bad! include Header.h 

file2.cpp

#include "Source.cpp" //error symbol already define! 
相關問題