原文:http://www.cnblogs.com/ider/archive/2011/06/30/what_is_in_cpp_header_and_implementation_file.html
在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++就了解不深,所以也沒有深刻的印象。
經過幾個項目的試煉和體驗之後,我又拿出這個問題問導師,他又一次耐心地給我講解了一遍(我發誓他絕對不是忘記了我曾經問過同樣的問題),這次我把它記錄了下來。
為了不再忘記,我將它們總結在這裡。
*申明:declaration
*定義:definition
頭文件的所有內容,都必須包含在
#ifndef {Filename}這樣才能保證頭文件被多個其他文件引用(include)時,內部的數據不會被多次定義而造成錯誤
在頭文件中,可以對函數用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#的風格,我比較喜歡把它們都寫在類內部,也因為在開發過程中,所使用的編輯器都有一個強大的功能:代碼折疊。
當然還有其他原因就是寫在類外部,對於每一個函數成員的實現都需要把模板類型作為限定符寫一遍,把類名限定符也要寫一遍。
=========================================================================================================
謝 邀,這個問題讓我想起我在實習的時候犯的一個錯誤,就是把模版類的定義和實現分開寫了,結果編譯出錯,查了兩天才查出問題。
C++中每一個對象所占用的空間大小,是在編譯的時候就確定的,在模板類沒有真正的被使用之前,編譯器是無法知道,模板類中使用模板類型的對象的所占用的空間的大小的。只有模板被真正使用的時候,編譯器才知道,模板套用的是什麼類型,應該分配多少空間。這也就是模板類為什麼只是稱之為模板,而不是泛型的緣故。
既然是在編譯的時候,根據套用的不同類型進行編譯,那麼,套用不同類型的模板類實際上就是兩個不同的類型,也就是說,stack<int>和stack<char>是兩個不同的數據類型,他們共同的成員函數也不是同一個函數,只不過具有相似的功能罷了。如上圖所示,很簡短的六行代碼,用的是STL裡面的stack,stack<int>和stack<char>的默認構造函數和push函數的入口地址是不一樣的,而不同的stack<int>對象相同的函數入口地址是一樣的,這個也反映了模板類在套用不同類型以後,會被編譯出不同代碼的現象。
所以模板類的實現,脫離具體的使用,是無法單獨的編譯的;把聲明和實現分開的做法也是不可取的,必須把實現全部寫在頭文件裡面。為了清晰,實現可以不寫在class後面的花括號裡面,可以寫在class的外面。
================================
還是把頭文件的內容都放在#ifndef和#endif中吧。不管你的頭文件會不會被多個文件引用,你都要加上這個。一般格式是這樣的:
#ifndef <標識>
#define <標識>
......
......
#endif
<標識>在理論上來說可以是自由命名的,但每個頭文件的這個“標識”都應該是唯一的。標識的命名規則一般是頭文件名全大寫,前後加下劃線,並把文件名中的“.”也變成下劃線,如:stdio.h
#ifndef _STDIO_H_
#define _STDIO_H_
......
#endif
2.在#ifndef中定義變量出現的問題(一般不定義在#ifndef中)。
#ifndef AAA
#define AAA
...
int i;
...
#endif
裡面有一個變量定義
在vc中鏈接時就出現了i重復定義的錯誤,而在c中成功編譯。
結論:
(1).當你第一個使用這個頭的.cpp文件生成.obj的時候,int i 在裡面定義了當另外一個使用這個的.cpp再次[單獨]生成.obj的時候,int i 又被定義然後兩個obj被另外一個.cpp也include 這個頭的,連接在一起,就會出現重復定義.
(2).把源程序文件擴展名改成.c後,VC按照C語言的語法對源程序進行編譯,而不是C++。在C語言中,若是遇到多個int i,則自動認為其中一個是定義,其他的是聲明。
(3).C語言和C++語言連接結果不同,可能(猜測)時在進行編譯的時候,C++語言將全局
變量默認為強符號,所以連接出錯。C語言則依照是否初始化進行強弱的判斷的。(參考)
解決方法:
(1).把源程序文件擴展名改成.c。
(2).推薦解決方案:
.h中只聲明 extern int i;在.cpp中定義
<x.h>
#ifndef __X_H__
#define __X_H__
extern int i;
#endif //__X_H__
<x.c>
int i;
頭文件一般存放函數或數據的說明。對應的c文件為具體實現,當然,也可以寫在一起。如:
compare.h文件代碼:
#include<stdio.h>
int compare(int a,int b){ if(a>b) printf("%d is biger than %d \n",a,b); if(a==b) printf("%d is equal %d \n",a,b); if(a<b) printf("%d is smaller than %d \n",a,b); return 0;}
主函數代碼:
#include "compare.h"
int main(int argc, char *argv[]){ int a=12,b=13; compare(a,b); system("PAUSE"); return 0;}
在C++編程過程中,隨著項目的越來越大,代碼也會越來越多,並且難以管理和分析。於是,在C++中就要分出了頭(.h)文件和實現(.cpp)文件,並且也有了Package 的概念。 對於以C 起步,C#作為“母語”的我剛開始跟著導師學習C++對這方面還是感到很模糊。雖然我可以以C 的知識面對C++的語法規范,用C#的思想領悟C++中類的使用。但是C#中定義和實現是都在一個文件中(其實都是在類裡面),而使用C 的時候也只是編程的剛剛起步,所寫的程序也只要一個文件就夠了。因此對於C++的Package 理解以及.h 文件和.cpp 文件的總是心存糾結。 幸好導師有詳細的PPT 讓我了解,一次對於Package 的認識就明白多了。簡單講,一個Pack age 就是由同名的.h 和.cpp 文件組成。當然可以少其中任意一個文件:只有.h 文件的Packag e 可以是接口或模板(template)的定義;只有.cpp 文件的Package 可以是一個程序的入口。 當然更具體詳細的講解,歡迎下載導師的教學PPT-Package 來了解更多。 不過我在這裡想講的還是關於.h 文件和.cpp 文件 知道Package 只是相對比較宏觀的理解:我們在項目中以Package 為編輯對象來擴展和修正我們的程序。編寫代碼時具體到應該把什麼放到.h 文件,又該什麼放在.cpp 文件中,我又迷惑了。 雖然Google 給了我很多的鏈接,但是大部分的解釋都太籠統了:申明寫在.h 文件,定義實現寫在.cpp 文件。這個解釋沒有差錯,但是真正下手起來,又會發現不知道該把代碼往哪裡打。 於是我又把這個問題拋給了導師,他很耐心地給我詳詳細細地表述了如何在C++中進行代碼分離。很可惜,第一次我聽下了,但是沒有聽太懂,而且本來對C++就了解不深,所以也沒有深刻的印象。 經過幾個項目的試煉和體驗之後,我又拿出這個問題問導師,他又一次耐心地給我講解了一遍(我發誓他絕對不是忘記了我曾經問過同樣的問題),這次我把它記錄了下來。 為了不再忘記,我將它們總結在這裡。 概覽 非模板類型(none-template) 模板類型(template) 頭文件 (.h) 全局變量申明(帶extern 限定符) 全局函數的申明 帶inline 限定符的全局函數的 帶inline 限定符的全局模板函數的申明和定義 非模板類型(none-template) 模板類型(template) 定義 類的定義 類函數成員和數據成員的申明(在類內部) 類定義內的函數定義(相當於i nline) 帶static const 限定符的數據成員在類內部的初始化 帶inline 限定符的類定義外的函數定義 模板類的定義 模板類成員的申明和定義(定義可以放在類內或者類外,類外不需要寫inline) 實現文件 (.cpp) 全局變量的定義(及初始化) 全局函數的定義 (無) 類函數成員的定義 類帶static 限定符的數據成員的初始化 *申明:declaration *定義:definitio 頭文件 頭文件的所有內容,都必須包含在 #ifndef {Filename} #define {Filename} //{Content of head file} #endif 這樣才能保證頭文件被多個其他文件引用(include)時,內部的數據不會被多次定義而造成錯誤 inline 限定符 在頭文件中,可以對函數用inline 限定符來告知編譯器,這段函數非常的簡單,可以直接嵌入到調用定義之處。 當然inline 的函數並不一定......余下全文>>