C++中頭文件(.h)和源文件(.cpp)都應該寫些什麼

頭文件(.h):

    寫類的聲明(包括類裏面的成員和方法的聲明)、函數原型、#define常數等,但通常來講不寫出具體的實現。html

    在寫頭文件時須要注意,在開頭和結尾處必須按照以下樣式加上預編譯語句(以下):ios

#ifndef CIRCLE_H #define CIRCLE_H
//你的代碼寫在這裏
#endif

    這樣作是爲了防止重複編譯,不這樣作就有可能出錯。程序員

    至於CIRCLE_H這個名字其實是無所謂的,你叫什麼都行,只要符合規範都行。原則上來講,很是建議把它寫成這種形式,由於比較容易和頭文件的名字對應。編程

   源文件(.cpp):編輯器

    源文件主要寫實現頭文件中已經聲明的那些函數的具體代碼。須要注意的是,開頭必須#include一下實現的頭文件,以及要用到的頭文件。那麼當你須要用到本身寫的頭文件中的類時,只須要#include進來就好了。ide

    下面舉個最簡單的例子來描述一下,咱就求個圓面積。模塊化

     第1步,創建一個空工程(以在VS2003環境下爲例)。函數

     第2步,在頭文件的文件夾裏新建一個名爲Circle.h的頭文件,它的內容以下:學習

#ifndef CIRCLE_H #define CIRCLE_H
class Circle { private: double r;//半徑
public: Circle();//構造函數
    Circle(double R);//構造函數
    double Area();//求面積函數
}; #endif

   注意到開頭結尾的預編譯語句。在頭文件裏,並不寫出函數的具體實現。測試

    第3步,要給出Circle類的具體實現,所以,在源文件夾裏新建一個Circle.cpp的文件,它的內容以下:

#include "Circle.h" Circle::Circle() { this->r=5.0; } Circle::Circle(double R) { this->r=R; } double Circle:: Area() { return 3.14*r*r; }

     須要注意的是:開頭處包含了Circle.h,事實上,只要此cpp文件用到的文件,都要包含進來!這個文件的名字其實不必定要叫Circle.cpp,但很是建議cpp文件與頭文件相對應。

    最後,咱們建一個main.cpp來測試咱們寫的Circle類,它的內容以下:

#include <iostream> #include "Circle.h"
using namespace std; int main() { Circle c(3); cout<<"Area="<<c.Area()<<endl; return 1; }

    注意到開頭時有#include "Circle.h"的聲明,證實咱們使用到了咱們剛纔寫的Circle類。

   至此,咱們工程的結構爲:

    運行一下,輸出結果爲:

   說明咱們寫的Circle類確實能夠用了。

一、.h叫作頭文件,它是不能被編譯的。「#include」叫作編譯預處理指令,能夠簡單理解成,在1.cpp中的#include"1.h"指令把1.h中的代碼在編譯前添加到了1.cpp的頭部。每一個.cpp文件會被編譯,生成一個.obj文件,而後全部的.obj文件連接起來你的可執行程序就算生成了。

發現了沒有,你要在.h文件中嚴格區分聲明語句和定義語句。好的習慣是,頭文件中應只處理常量、變量、函數以及類等等等等的聲明,變量的定義和函數的實現等等等等都應該在源文件.cpp中進行。

至於.h和.cpp具備一樣的主文件名的狀況呢,對編譯器來說是沒有什麼意義的,編譯器不會去匹配兩者的主文件名,相反它很傻,只認#include等語句。可是這樣寫是一種約定俗成的編程風格,一個類的名字做爲其頭文件和源文件的主文件名好比Class1.h和Class1.cpp,這個類的聲明在Class1.h中,實如今Class1.cpp中,咱們人類看起來比較整齊,讀起來方便,也頗有利於模塊化和源代碼的重用。

爲何這個風格會約定俗成?有一句著名的話,叫「程序是爲程序員寫的」。

二、.h文件和.cpp文件也就是說,在h文件中聲明Declare,而在cpp文件中定義Define。 「聲明」向計算機介紹名字,它說,「這個名字是什麼意思」。而「定義」爲這個名字分配存儲空間。不管涉及到變量時仍是函數時含義都同樣。不管在哪一種狀況下,編譯器都在「定義」處分配存儲空間。對於變量,編譯器肯定這個變量佔多少存儲單元,並在內存中產生存放它們的空間。對於函數,編譯器產生代碼,併爲之分配存儲空間。函數的存儲空間中有一個由使用不帶參數表或帶地址操做符的函數名產生的指針。定義也能夠是聲明。若是該編譯器尚未看到過名字A,程序員定義int A,則編譯器立刻爲這個名字分配存儲地址。聲明經常使用於extern關鍵字。若是咱們只是聲明變量而不是定義它,則要求使用extern。對於函數聲明, extern是可選的,不帶函數體的函數名連同參數表或返回值,自動地做爲一個聲明。

另篇:

在C++編程過程當中,隨着項目的愈來愈大,代碼也會愈來愈多,而且難以管理和分析。因而,在C++中就要分出了頭(.h)文件和實現(.cpp)文件,而且也有了Package的概念。

對於以C起步,C#做爲「母語」的我剛開始跟着導師學習C++對這方面仍是感到很模糊。雖然我能夠以C的知識面對C++的語法規範,用C#的思想領悟C++中類的使用。可是C#中定義和實現是都在一個文件中(其實都是在類裏面),而使用C的時候也只是編程的剛剛起步,所寫的程序也只要一個文件就夠了。所以對於C++的Package理解以及.h文件和.cpp文件的老是心存糾結。

幸虧導師有詳細的PPT讓我瞭解,一次對於Package的認識就明白多了。簡單講,一個Package就是由同名的.h和.cpp文件組成。固然能夠少其中任意一個文件:只有.h文件的Package能夠是接口或模板(template)的定義;只有.cpp文件的Package能夠是一個程序的入口。

固然更具體詳細的講解,歡迎下載導師的教學PPT-Package來了解更多。

不過我在這裏想講的仍是關於.h文件和.cpp文件

知道Package只是相對比較宏觀的理解:咱們在項目中以Package爲編輯對象來擴展和修正咱們的程序。編寫代碼時具體到應該把什麼放到.h文件,又該什麼放在.cpp文件中,我又迷惑了。

雖然Google給了我不少的連接,可是大部分的解釋都太籠統了:申明寫在.h文件,定義實現寫在.cpp文件。這個解釋沒有差錯,可是真正下手起來,又會發現不知道該把代碼往哪裏打。

因而我又把這個問題拋給了導師,他很耐心地給我詳詳細細地表述瞭如何在C++中進行代碼分離。很惋惜,第一次我聽下了,可是沒有聽太懂,並且原本對C++就瞭解不深,因此也沒有深入的印象。

通過幾個項目的試煉和體驗以後,我又拿出這個問題問導師,他又一次耐心地給我講解了一遍(我發誓他絕對不是忘記了我曾經問過一樣的問題),此次我把它記錄了下來。

爲了避免再忘記,我將它們總結在這裏。

概覽

  非模板類型(none-template) 模板類型(template)
頭文件(.h)
  • 全局變量申明(帶extern限定符)
  • 全局函數的申明
  • inline限定符的全局函數的定義
  • 類的定義
  • 類函數成員和數據成員的申明(在類內部)
  • 類定義內的函數定義(至關於inline)
  • static const限定符的數據成員在類內部的初始化
  • inline限定符的類定義外的函數定義
  • 模板類的定義
  • 模板類成員的申明和定義(定義能夠放在類內或者類外,類外不須要寫inline)
實現文件(.cpp)
  • 全局變量的定義(及初始化)
  • 全局函數的定義
(無)
  • 類函數成員的定義
  • 類帶static限定符的數據成員的初始化

*申明:declaration
*定義:definition

頭文件

頭文件的全部內容,都必須包含在

#ifndef {Filename} 
#define {Filename} 

//{Content of head file} 

#endif

這樣才能保證頭文件被多個其餘文件引用(include)時,內部的數據不會被屢次定義而形成錯誤

inline限定符

在頭文件中,能夠對函數用inline限定符來告知編譯器,這段函數很是的簡單,能夠直接嵌入到調用定義之處。

固然inline的函數並不必定會被編譯器做爲inline來實現,若是函數過於複雜,編譯器也會拒絕inline。

所以簡單說來,代碼最好短到只有3-5行的才做爲inline。有循環,分支,遞歸的函數都不要用作inline。

對於在類定義內定義實現的函數,編譯器自動當作有inline請求(也是不必定inline的)。所以在下邊,我把帶有inline限定符的函數成員和寫在類定義體內的函數成員統稱爲「要inline的函數成員」

非模板類型

全局類型

就像前面籠統的話講的:申明寫在.h文件。

對於函數來說,沒有實現體的函數,就至關因而申明;而對於數據類型(包括基本類型和自定義類型)來講,其申明就須要用extern來修飾。

而後在.cpp文件裏定義、實現或初始化這些全局函數和全局變量。

不過導師一直反覆強調:不準使用全局函數和全局變量。用了以後形成的後果,目前就是交上去的做業項目會扣分。固然不能用自有不能用的理由以及解決方案,不過不在目前的討論範圍內。

 

自定義類型

對於自定義類型,包括類(class)和結構體(struct),它們的定義都是放在.h文件中。其成員的申明和定義就比較複雜了,不過看上邊的表格,仍是比較清晰的。

函數成員

函數成員不管是否帶有static限定符,其申明都放在.h文件的類定義內部。

對於要inline的函數成員其定義放在.h文件;其餘函數的實現都放在.cpp文件中。

數據成員

數據成員的申明與定義都是放在.h文件的類定義內部。對於數據類型,關鍵問題是其初始化要放在什麼地方進行。

對於只含有static限定符的數據成員,它的初始化要放在.cpp文件中。由於它是全部類對象共有的,所以必須對它作合適的初始化。

對於只含有const限定符的數據成員,它的初始化只能在構造函數的初始化列表中完成。由於它是一經初始化就不能從新賦值,所以它也必須進行合適的初始化。

對於既含有static限定符,又含有const限定符的數據成員,它的初始化和定義同時進行。它也是必須進行合適的初始化

對於既沒有static限定符,又沒有const限定符的數據成員,它的值只針對本對象能夠隨意修改,所以咱們並不在乎它的初始化何時進行。

模板類型

C++中,模板是一把開發利器,它與C#,Java的泛型很類似,卻又不盡相同。之前,我一直只以爲像泛型,模板這種東西我可能一生也不可能須要使用到。可是在導師的強制逼迫使用下,我才真正體會到模板的強大,也真正知道要如何去使用模板,更進一步是如何去設計模板。不過這不是三言兩語能夠講完的,就很少說了。

對於模板,最重要的一點,就是在定義它的時候,編譯器並不會對它進行編譯,由於它沒有一個實體可用。

只有模板被具體化(specialization)以後(用在特定的類型上),編譯器纔會根據具體的類型對模板進行編譯。

因此才定義模板的時候,會發現編譯器基本不會報錯(我當時還很開心的:我寫代碼盡然會沒有錯誤,一鼓作氣),也作不出智能提示。可是當它被具體用在一個類上以後,錯誤就會大片大片的出現,卻每每沒法準肯定位。

所以設計模板就有設計模板的一套思路和方式,可是這跟本文的主題也有偏。

 

由於模板的這種特殊性,它並無本身的準肯定義,所以咱們不能把它放在.cpp文件中,而要把他們所有放在.h文件中進行書寫。這也是爲了在模板具體化的時候,可以讓編譯器能夠找到模板的全部定義在哪裏,以便真正的定義方法。

至於模板類函數成員的定義放在哪裏,導師的意見是放在類定義以外,由於這樣當你看類的時候,一目瞭然地知道有那些方法和數據;我在用Visual Studio的時候查看到其標準庫的實現,都是放在類內部的。

多是我習慣了C#的風格,我比較喜歡把它們都寫在類內部,也由於在開發過程當中,所使用的編輯器都有一個強大的功能:代碼摺疊。

固然還有其餘緣由就是寫在類外部,對於每個函數成員的實現都須要把模板類型做爲限定符寫一遍,把類名限定符也要寫一遍。

相關文章
相關標籤/搜索