簡單來說,要了解C文件和頭文件(即.h)的區(qū)別,首先需要了解編譯器的工作過程。 一般來說,編譯器會做以下幾個過程:
1.預(yù)處理階段
2. 詞法分析階段
3、編譯階段,先編譯成純匯編語句,再編譯成與CPU相關(guān)的二進制代碼,生成各個目標(biāo)文件(.obj文件)
4、在連接階段,對每個目標(biāo)文件中的每段代碼進行絕對地址定位,生成特定平臺相關(guān)的可執(zhí)行文件。 當(dāng)然也可以用于最后生成純二進制代碼,即去掉文件格式信息。 (生成.exe文件)
編譯器在編譯的時候是以C文件為單位的,也就是說,如果你的工程中沒有C文件,那么你的工程是不會被編譯的,而鏈接器是以目標(biāo)文件為單位的,它會每個目標(biāo)文件重新定位函數(shù)和變量以生成最終的可執(zhí)行文件。 PC端的程序開發(fā)一般都有一個main函數(shù)。 這是每個編譯器的約定。 當(dāng)然,如果你自己寫鏈接描述文件,你可以使用main函數(shù)作為程序入口! ! ! !
(main.c文件目標(biāo)文件可執(zhí)行)
有了這些基礎(chǔ)知識,我們言歸正傳,為了生成最終的可執(zhí)行文件,我們需要一些目標(biāo)文件,也就是C文件,而這些C文件需要一個main函數(shù)作為可執(zhí)行程序的入口,那么我們將從一個C文件開始,假設(shè)這個C文件的內(nèi)容如下:
#
# “。H”
int main(int argc, char **argv)
{
測試 = 25;
("測試............%d\n",測試);
}
.h頭文件內(nèi)容如下:
內(nèi)部測試;
現(xiàn)在以這個例子來說明編譯器的工作:
1.在預(yù)處理階段,編譯器以C文件為單位,首先讀取C文件,發(fā)現(xiàn)第一句和第二句包含頭文件,就會在所有搜索路徑中搜索這兩個文件,并找到它們,它會處理對應(yīng)頭文件中的宏、變量、函數(shù)聲明、嵌套頭文件包含等,檢測依賴關(guān)系,進行宏替換,看是否有重復(fù)的定義和聲明,最后將那些文件放入All這些東西被掃描到當(dāng)前的 C 文件中,形成一個中間“C 文件”
2.在編譯階段,在上一步中,相當(dāng)于把頭文件中的test變量掃描到一個中間C文件中,那么test變量就變成了這個文件中的全局變量,此時所有的中間C文件的所有變量和函數(shù)分配空間,將每個函數(shù)編譯成二進制代碼,并根據(jù)特定的目標(biāo)文件格式生成目標(biāo)文件。 在這種格式的目標(biāo)文件中,對各個全局變量和函數(shù)進行符號描述,并將這些二進制代碼按照一定的編譯標(biāo)準(zhǔn)組織成一個目標(biāo)文件
3. 在連接階段,根據(jù)一些參數(shù)將上一步生成的目標(biāo)文件連接起來,生成最終的可執(zhí)行文件。 主要任務(wù)是將各個目標(biāo)文件的函數(shù)、變量等重新定位,相當(dāng)于將二進制代碼按照一定的規(guī)范組合成一個文件,再回到C文件寫什么的話題頭文件:理論上來說,只要C文件和頭文件里的內(nèi)容是C語言支持的,不管寫什么都可以,比如你把函數(shù)體寫在頭文件里,只要這個頭文件包含在任何C文件中,函數(shù)就可以編譯成目標(biāo)文件的一部分(編譯是基于C文件的,如果不在任何C文件中如果這個頭文件包含在,這段代碼沒用),你可以在C文件中進行函數(shù)聲明、變量聲明和結(jié)構(gòu)聲明,這不是問題! ! !
那為什么一定要分頭文件和C文件呢? 為什么函數(shù)、變量聲明、宏聲明和結(jié)構(gòu)聲明一般都在頭文件中進行? 而在C文件中定義變量,函數(shù)實現(xiàn)呢? ? 原因如下:
1.如果一個函數(shù)體是在頭文件中實現(xiàn)的,如果在多個C文件中被引用,同時編譯多個C文件,生成的目標(biāo)文件會鏈接成一個可執(zhí)行文件,每次引用this在頭文件的C文件生成的目標(biāo)文件中,有一段這個函數(shù)的代碼。 如果這個函數(shù)沒有定義為局部函數(shù),那么在鏈接的時候,會發(fā)現(xiàn)多個相同的函數(shù),就會報錯。
2、如果在頭文件中定義了一個全局變量,并為該全局變量賦了初值,那么在引用這個頭文件的多個C文件中,存在同名變量名的副本。 關(guān)鍵是給變量賦初值。 因此,編譯器會將這個變量放入DATA段。 最后,在連接階段,DATA段中會出現(xiàn)多個相同的變量。 它不能將這些變量統(tǒng)一為一個變量,即只為這個變量分配一個空間。 而不是多個空格,如果這個變量在頭文件中沒有賦初值,編譯器會把它放在
在BSS段中,鏈接器只會為BSS段中的多個同名變量分配一個存儲空間。
3、如果在一個C文件中聲明了宏、結(jié)構(gòu)體、函數(shù)等,那么如果我要在另一個C文件中引用相應(yīng)的宏和結(jié)構(gòu)體,就必須再做一次重復(fù)的工作。 如果我改了一個C文件而忘記改其他C文件中的語句,那問題就大了,程序的邏輯會變得你無法想象。 如果你把這些的東西放在一個頭文件里,你要用它的C文件,只需要引用一個就OK了! ! !這不是很方便嗎? 當(dāng)你想改變一個語句時,你只需要移動頭文件。
4.在頭文件中聲明結(jié)構(gòu)、函數(shù)等。 當(dāng)你需要把你的代碼打包成一個庫讓別人使用你的代碼,而你又不想把源碼公布出來,別人怎么用你的庫呢? 也就是如何使用你庫中的每一個函數(shù)? ? 一種方法是將源代碼公開,其他人可以隨意使用。 另一種是提供頭文件,別人可以從頭文件中讀到你的函數(shù)原型,從而知道如何調(diào)用你寫的函數(shù),就像你調(diào)用函數(shù)一樣。 ,里面的參數(shù)是什么? ? 你怎么知道? ? 不是看別人頭文件中的相關(guān)語句! ! ! 當(dāng)然,這些東西都成了C標(biāo)準(zhǔn),即使不看別人的頭文件,也能知道怎么用。
C語言中.c和.h文件的混淆
本質(zhì)上沒有什么區(qū)別。只是籠統(tǒng)地說:.h文件是一個頭文件,里面包含了函數(shù)聲明、宏定義、結(jié)構(gòu)體定義等。
.c文件是程序文件,包含函數(shù)實現(xiàn)、變量定義等內(nèi)容。 并且后綴是什么并不重要,但是編譯器會默認(rèn)對具有某些后綴的文件采取某些操作。 您可以強制編譯器將任何后綴的文件編譯為c文件。
將這兩個文件分開編寫是一種很好的編程風(fēng)格。
而且,比如我在aaa.h中定義了一個函數(shù)聲明,然后我在aaa.h的同級目錄下創(chuàng)建了aaa.c。 這個函數(shù)的實現(xiàn)定義在aaa.c中,然后main函數(shù)位于.c文件中的#this aaa.h,然后我就可以使用這個函數(shù)了。 Main 會在運行時找到這個定義這個函數(shù)的 aaa.c 文件。
這是因為:
main函數(shù)是標(biāo)準(zhǔn)C/C++程序的入口,編譯器會先找到該函數(shù)所在的文件。
假設(shè)編譯器在編譯.c(包括main())時,找到mylib.h(里面聲明了函數(shù)void test()),那么編譯器就會按照預(yù)設(shè)的路徑(路徑列表和代碼文件所在的路徑) )尋找同名的實現(xiàn)文件(擴展名.cpp或.c,本例為mylib.c),如果找到,找到其中的函數(shù)(本例為void test()),然后繼續(xù)編譯; 如果在指定目錄下找不到實現(xiàn)文件,或者在本文件及后續(xù)文件中找不到實現(xiàn)代碼,則返回編譯錯誤。 其實這個過程可以“看”成一個文件拼接過程,聲明和實現(xiàn)分別寫在頭文件和C文件中,或者兩者同時寫在頭文件中,理論上是沒有的本質(zhì)區(qū)別。
以上就是所謂的動態(tài)方法。
對于靜態(tài)方法,基本上所有的C/C++編譯器都支持一種叫做Link的鏈接方法,也就是所謂的靜態(tài)鏈接。
這樣,我們所要做的就是編寫包含函數(shù)、類等聲明的頭文件(ah、bh、...)及其對應(yīng)的實現(xiàn)文件(a.cpp、b.cpp、...)。 .), 編譯器會把它編譯成靜態(tài)庫文件(a.lib,b.lib,...)。 在后續(xù)的代碼復(fù)用過程中,我們只需要提供相應(yīng)的頭文件(.h)和相應(yīng)的庫文件(.lib),就可以使用過去的代碼了。
與動態(tài)方法相比,靜態(tài)方法的優(yōu)勢在于實現(xiàn)代碼的隱蔽性,即C++所提倡的“接口對外,實現(xiàn)代碼不可見”。 有利于庫文件的轉(zhuǎn)發(fā)。
許多人會反對說難題中最難的部分是基本概念,但事實確實如此。 高中學(xué)物理的時候,老師講的是概念,概念要搞清楚,難的就變簡單了。 如果你能分析清楚一個物理問題有幾個物理過程,每個過程都遵守那個物理定律(比如動量守恒、牛二定律、能量守恒定律),那么很容易把這個過程的方程按照定律 ,N個過程一定是N個N元方程,問題就很容易解決了。 就算是高中物理競賽題,最難的也無非是:
(一)混淆概念,導(dǎo)致無法分析幾個物理過程,或者某個物理過程所遵循的物理規(guī)律;
(2) 有高階方程,方程即使列出也解不出來。 后者已經(jīng)屬于數(shù)學(xué)的范疇,所以最難的是要把握清楚的概念;
編程也是如此。 概念清楚的話,基本沒有問題(數(shù)學(xué)上會比較難,比如算法的選擇,時間空間和效率的權(quán)衡,穩(wěn)定性和資源的平衡)。 然而,要掌握一個清晰的概念卻不是那么容易。 看看下面的例子,看看你是否有一個清晰透徹的理解。
//啊無效 foo(); //ac # "ah" //我的問題出來了:這句話到底有沒有必要?
無效的 foo()
{ ; } //main.c # "ah" int main(int argc, char *argv[]) {foo(); 0; }
對于上面的代碼,請回答三個問題:
. (1)ac中#“啊”這句是多余的嗎?
(2)為什么在xx.c中經(jīng)常看到對應(yīng)的xx.h?
(3)如果不是用ac寫的,編譯器會自動把.h文件的內(nèi)容綁定到同名的.c文件中嗎? (慣于)
(以上3個問題請仔細(xì)思考10分鐘,不要急著看下面的解釋。:) 想的越多理解的越深。 )
好了,是時候了! 請忘掉以上3個問題以及你對這三個問題的想法,然后慢慢聽我說。 正確的觀念是:在C編譯器看來,.h和.c都是浮云,改名為.txt或.doc也沒有太大區(qū)別。 換句話說,.h和.c之間沒有必然聯(lián)系。 一般.h包含定義在同名.c文件中的變量、數(shù)組、函數(shù)的聲明,以及需要在.c之外使用的聲明。 這個語句有什么用? 只是為了便于參考需要這些聲明的地方。 因為宏#“xx.h”的實際意思是刪除當(dāng)前行,在當(dāng)前行位置原封不動的插入xx.h中的內(nèi)容。 由于我要寫這些函數(shù)聲明的地方很多(xx.c中每一個調(diào)用函數(shù)的地方都必須在使用前聲明),所以使用#"xx.h"宏可以簡化很多行代碼——讓預(yù)處理器替換自身。 也就是說,xx.h其實只是調(diào)用了xx.c中需要寫函數(shù)聲明的地方(可以少寫幾行)。 至于這個.h文件是誰的,是.h還是.c,或者跟這個.h有關(guān)系嗎?同名的.c沒有必然關(guān)系。
所以你可能會說:嗯? 然后我平時就是想調(diào)用xx.c中的某個函數(shù),但是把xx.h文件刪掉了。 宏替換后不是有很多無用的語句嗎? 是的,確實引入了很多垃圾,但是卻省了很多筆墨,整個布局看起來也干凈多了。 這就是魚和熊掌不可兼得的原因。 反正多聲明(.h一般只用來放聲明,不放定義,看我的書《過馬路,左看右看》)無傷大雅,不會影響編譯,何樂而不為呢?
回過頭來看以上三個問題,是不是很容易回答呢? 答:不一定。 這個例子顯然是多余的。 但是如果.c中的函數(shù)還需要調(diào)用同一個.c中的其他函數(shù),那么這個.c往往會和.h同名,所以不用擔(dān)心聲明和調(diào)用的順序(C 要求在使用前必須聲明,同名的.h一般會放在.c)的開頭。 許多項目甚至同意將這種寫法作為代碼規(guī)范來規(guī)范清晰的代碼。
答:1已經(jīng)回答了。
答:沒有。問這個問題的人肯定是不清楚,或者就是想渾水摸魚。 很煩人的是中國很多考試都有這樣的爛題。 生怕別人概念清楚,肯定會把考生搞糊涂。
搞清楚語法和概念說起來容易做起來難。 有三招:工作不要發(fā)呆,抽空多思考,多看書;
讀書要讀好書,問人要問強人。 壞書壞人會給你錯誤的觀念,誤導(dǎo)你;
勤能補拙是一種很好的修養(yǎng),一分耕耘一分收獲;
(1) 通過頭文件調(diào)用庫函數(shù)。 在很多場合,源代碼是不方便(或不允許)發(fā)布給用戶的,只要提供頭文件和二進制庫給用戶即可。 用戶只需要根據(jù)頭文件中的接口聲明調(diào)用庫函數(shù)即可,無需關(guān)心接口是如何實現(xiàn)的。 編譯器從庫中提取相應(yīng)的代碼。
(2) 頭文件可以加強類型安全檢查。 如果接口的實現(xiàn)或使用方式與頭文件中的聲明不一致,編譯器會指出錯誤。 這個簡單的規(guī)則可以大大減輕程序員調(diào)試和改錯的負(fù)擔(dān)。
頭文件用于存儲函數(shù)原型。
頭文件與源文件有什么關(guān)系?
這道題其實是說已知的頭文件“啊”聲明了一系列函數(shù)(只有函數(shù)原型,沒有函數(shù)實現(xiàn)),而這些函數(shù)是在“b.cpp”中實現(xiàn)的,那么如果我要在“c.cpp”中在"ah"中聲明的"b.cpp"中實現(xiàn)的函數(shù)通常在"c.cpp"#"ah"中使用,那么c.cpp如何找到b.cpp中的實現(xiàn)呢?
事實上,.cpp 和.h 文件名沒有任何直接關(guān)系,許多編譯器可以接受其他擴展名。
譚浩強先生在《C程序設(shè)計》一書中提到,編譯器在進行預(yù)處理時,需要對#命令進行“文件包含處理”:將.h的全部內(nèi)容復(fù)制到#
“。H”。 這也解釋了為什么很多編譯器不關(guān)心這個文件的后綴是什么——因為#是為了完成一個“復(fù)制和插入代碼”的工作。
程序編譯時不會在b.cpp文件中尋找函數(shù)實現(xiàn),只有在鏈接時才做這個工作。 我們在b.cpp或c.cpp中使用#"ah"來實際引入相關(guān)聲明,這樣編譯就可以通過,程序不關(guān)心它是在哪里實現(xiàn)的,是如何實現(xiàn)的。 源文件編譯后,生成目標(biāo)文件(.o 或.obj 文件)。 在目標(biāo)文件中,這些函數(shù)和變量被視為符號。 鏈接的時候需要指定需要連接哪個.o或者.obj文件(這里是b.cpp生成的.o或者.obj文件),這時候鏈接器會去這個.o或者.obj file 查找b.cpp中實現(xiàn)的函數(shù),并構(gòu)建到.cpp中指定的可執(zhí)行文件中。 (很重要)
在VC中,一堆情況不需要你自己寫,你只需要包含所有需要的文件,VC會自動幫你寫。
通常,編譯器會在每個 .o 或 .obj 文件中查找所需的符號,而不是只查找某個文件或找到一個文件就不找。 因此,如果在幾個不同的文件中實現(xiàn)了同一個功能,或者定義了同一個全局變量,鏈接時會提示“”。
最后,無論你是在職場成長還是在大學(xué)的入門階段,C/C++都是一門編程語言,不僅可以增強你的思維能力,還可以為編程打下堅實的基礎(chǔ)。 如果你想做軟件開發(fā),成為核心程序員, C/C++是更好的選擇。 作者有一個千人C/C++程序員的Q群(Q船有線:C語言編程學(xué)習(xí)聚集地(默默建立))如果覺得自學(xué)C/C++語言有難度,有興趣的小伙伴學(xué)習(xí)或者學(xué)習(xí)C/C++編程的可以進來交流。 下面給大家分享一下C/C++的學(xué)習(xí)路線圖: