參考博客:
最近在做一個稍微有些復雜的項目。涉及到的函數、結構體、變量等比較多。通常,我編寫c/c++項目的方式是,有一個main.c文件,該文件的main函數作為接口,調用其他函數。所有其他函數按功能,分別放在不同的.h文件中,這樣的方式在編譯和運行上肯定是沒有什么問題的。
隨着項目編寫的深入,結構、邏輯以及變量的日趨復雜,總感覺上述方式的實現仍然不夠清晰並且會導致一些冗余。忽然記起,在課堂上,老師提到過,.h文件一般用來存放函數聲明和變量名,那么為什么我在.h文件中實現函數不會有問題呢?其他.c文件和main.c文件又有什么關系呢?這些,都使我不得不重新思考.h文件和.c文件的作用和關系。
要理清.h文件的作用,我們不妨看看.h文件的由來:
“在編譯器只認識.c(.cpp))文件,而不知道.h是何物的年代,那時的人們寫了很多的.c(.cpp)文件,漸漸地,人們發現在很多.c(.cpp)文件中的聲明語句就是相同的,但他們卻不得不一個字一個字地重復地將這些內容敲入每個.c(.cpp)文件。但更為恐怖的是,當其中一個聲明有變更時,就需要檢查所有的.c(.cpp)文件。
於是人們將重復的部分提取出來,放在一個新文件里,然后在需要的.c(.cpp)文件中敲入#include XXXX這樣的語句。這樣即使某個聲明發生了變更,也再不需要到處尋找與修改了。因為這個新文件,經常被放在.c(.cpp)文件的頭部,所以就給它起名叫做“頭文件”,擴展名是.h。
在我們語言的初學階段,往往我們的程序只有一個.c的文件或這很少的幾個,這時我們就很少遇到頭文件組織這個頭疼的問題,隨着我們程序的增加,代碼 量到了幾千行甚至幾萬行,文件數也越來越多。這時這些文件的組織就成了一個問題,其實說白了這些文件的組織問題從理論上來說是軟件工程中的模塊設計等等的問題。”(引自c語言項目中.h文件和.c文件的關系)
由上可以看出,.h文件最初就是用來給變量和函數提供一些全局性的聲明,這些聲明被其他.c文件共享,方便變量和聲明的修改,使得大型代碼邏輯更清晰更易於維護。因此.h文件中一般是聲明,很少有代碼的具體實現。
那么為什么在.h文件中實現函數也不會出錯呢?在.h文件中實現函數與在.c文件中實現函數有什么區別和聯系呢?普通的.C文件和包含main函數的c文件有什么區別和聯系呢?
要解決上述問題,首先必須弄清編譯器的工作原理。編譯器的最終目的是將程序員編寫的源代碼轉換成機器能夠識別運行的二進制機器碼。大體上分,可以分為4個步驟:
1.頭文件的預編譯,預處理
編譯器在編譯源代碼時,會先編譯頭文件,保證每個頭文件只被編譯一次。
在預處理階段,編譯器將c文件中引用的頭文件中的內容全部寫到c文件中。
2.詞法和語法分析(查錯)
3.編譯(匯編代碼,.obj文件)
轉化為匯編碼,這種文件稱為目標文件。后綴為.obj。
4.鏈接(二進制機器碼,.exe文件)
將匯編代碼轉換為機器碼,生成可執行文件。
更詳細具體的流程可參考編譯器的工作過程
在編譯過程中,.h文件中的所有內容會被寫到包含它的.c文件中,而所有的.c文件以一個共同的main函數作為可執行程序的入口。
因此,在.h文件中編寫函數實現並不會出錯,相當於所有.h的內容最后都被寫到了main.c文件中。
但是為了邏輯性、易於維護性以及一些其他目的(可參考c語言中.h文件和.c文件的解析 ),一般在.h文件中寫函數的聲明,在.c文件中編寫函數的實現。