天天看點

C語言中的頭檔案與原檔案

簡單的說其實要了解C檔案與頭檔案(即.h)有什麼不同之處,首先需要弄明白編譯器的工作過程,一般說來編譯器會做以下幾個過程:

1.預處理階段

2.詞法與文法分析階段

3.編譯階段,首先編譯成純彙編語句,再将之彙編成跟CPU相關的二進制碼,生成各個目标檔案 (.obj檔案)

4.連接配接階段,将各個目标檔案中的各段代碼進行絕對位址定位,生成跟特定平台相關的可執行檔案,當然,最後還可以用objcopy生成純二進制碼,也就是去掉了檔案格式資訊。(生成.exe檔案)

編譯器在編譯時是以C檔案為機關進行的,也就是說如果你的項目中一個C檔案都沒有,那麼你的項目将無法編譯,連接配接器是以目标檔案為機關,它将一個或多個目标檔案進行函數與變量的重定位,生成最終的可執行檔案,在PC上的程式開發,一般都有一個main函數,這是各個編譯器的約定,當然,你如果自己寫連接配接器腳本的話,可以不用main函數作為程式入口!!!!

(main .c檔案 目标檔案 可執行檔案 )

有了這些基礎知識,再言歸正傳,為了生成一個最終的可執行檔案,就需要一些目标檔案,也就是需要C檔案,而這些C檔案中又需要一個main函數作為可執行程式的入口,那麼我們就從一個C檔案入手,假定這個C檔案内容如下:

#include

#include "mytest.h"

int main(int argc,char **argv)

{

test = 25;

printf("test.................%d/n",test);

}

頭檔案内容如下:

int test;

現在以這個例子來講解編譯器的工作:

1.預處理階段:編譯器以C檔案作為一 個單元,首先讀這個C檔案,發現第一句與第二句是包含一個頭檔案,就會在所有搜尋路徑中尋找這兩個檔案,找到之後,就會将相應頭檔案中再去處理宏,變量, 函數聲明,嵌套的頭檔案包含等,檢測依賴關系,進行宏替換,看是否有重複定義與聲明的情況發生,最後将那些檔案中所有的東東全部掃描進這個目前的C檔案 中,形成一個中間“C檔案”

2.編譯階段,在上一步中相當于将那個頭檔案中的test變量掃描進了一個中 間C檔案,那麼test變量就變成了這個檔案中的一個全局變量,此時就将所有這個中間C檔案的所有變量,函數配置設定空間,将各個函數編譯成二進制碼,按照特 定目标檔案格式生成目标檔案,在這種格式的目标檔案中進行各個全局變量,函數的符号描述,将這些二進制碼按照一定的标準組織成一個目标檔案

3.連接配接階段,将上一步成生的各個目标檔案,根據一些參數,連接配接生成最終的可 執行檔案,主要的工作就是重定位各個目标檔案的函數,變量等,相當于将個目标檔案中的二進制碼按一定的規範合到一個檔案中再回到C檔案與頭檔案各寫什麼内 容的話題上:理論上來說C檔案與頭檔案裡的内容,隻要是C語言所支援的,無論寫什麼都可以的,比如你在頭檔案中寫函數體,隻要在任何一個C檔案包含此頭文 件就可以将這個函數編譯成目标檔案的一部分(編譯是以C檔案為機關的,如果不在任何C檔案中包含此頭檔案的話,這段代碼就形同虛設),你可以在C檔案中進 行函數聲明,變量聲明,結構體聲明,這也不成問題!!!那為何一定要分成頭檔案與C檔案呢?又為何一般都在頭件中進行函數,變量聲明,宏聲明,結構體聲明 呢?而在C檔案中去進行變量定義,函數實作呢??原因如下:

1.如果在頭檔案中實作一個函數體,那麼如果在多個C檔案中引用它,而且又同時編 譯多個C檔案,将其生成的目标檔案連接配接成一個可執行檔案,在每個引用此頭檔案的C檔案所生成的目标檔案中,都有一份這個函數的代碼,如果這段函數又沒有定 義成局部函數,那麼在連接配接時,就會發現多個相同的函數,就會報錯

2.如果在頭檔案中定義全局變量,并且将此全局變量賦初值,那麼在多個引用此 頭檔案的C檔案中同樣存在相同變量名的拷貝,關鍵是此變量被賦了初值,是以編譯器就會将此變量放入DATA段,最終在連接配接階段,會在DATA段中存在多個 相同的變量,它無法将這些變量統一成一個變量,也就是僅為此變量配置設定一個空間,而不是多份空間,假定這個變量在頭檔案沒有賦初值,編譯器就會将之放入 BSS段,連接配接器會對BSS段的多個同名變量僅配置設定一個存儲空間

3.如果在C檔案中聲明宏,結構體,函數等,那麼我要在另一個C檔案中引用相 應的宏,結構體,就必須再做一次重複的工作,如果我改了一個C檔案中的一個聲明,那麼又忘了改其它C檔案中的聲明,這不就出了大問題了,程式的邏輯就變成 了你不可想象的了,如果把這些公共的東東放在一個頭檔案中,想用它的C檔案就隻需要引用一個就OK了!!!這樣豈不友善,要改某個聲明的時候,隻需要動一 下頭檔案就行了

4.在頭檔案中聲明結構體,函數等,當你需要将你的代碼封裝成一個庫,讓别人來用你的代碼,你又不想公布源碼,那麼人家如何利 用你的庫呢?也就是如何利用你的庫中的各個函數呢??一種方法是公布源碼,别人想怎麼用就怎麼用,另一種是提供頭檔案,别人從頭檔案中看你的函數原型,這 樣人家才知道如何調用你寫的函數,就如同你調用printf函數一樣,裡面的參數是怎樣的??你是怎麼知道的??還不是看人家的頭檔案中的相關聲明 啊!!!當然這些東東都成了C标準,就算不看人家的頭檔案,你一樣可以知道怎麼使用

c語言中.c和.h檔案的困惑

本質上沒有任何差別。   隻不過一般:

.h檔案是頭檔案,内含函數聲明、宏定義、結構體定義等内容.c檔案是程式檔案,内含函數實作,變量定義等内容。而且是什麼字尾也沒有關系,隻不過編譯器會預設對某些字尾的檔案采取某些動作。你可以強制編譯器把任何字尾的檔案都當作c檔案來編。

這樣分開寫成兩個檔案是一個良好的程式設計風格。

而且,比方說 我在aaa.h裡定義了一個函數的聲明,然後我在aaa.h的同一個目錄下建立aaa.c , aaa.c裡定義了這個函數的實作,然後是在main函數所在.c檔案裡#include這個aaa.h  然後我就可以使用這個函數了。 main在運作時就會找到這個定義了這個函數的aaa.c檔案。這是因為:main函數為标準C/C++的程式入口,編譯器會先找到該函數所在的檔案。假定編譯程式編譯myproj.c(其中含main())時,發現它include了mylib.h(其中聲明了函數void test()),那麼此時編譯器将按照事先設定的路徑(Include路徑清單及代碼檔案所在的路徑)查找與之同名的實作檔案(擴充名為.cpp或.c,此例中為mylib.c),如果找到該檔案,并在其中找到該函數(此例中為void test())的實作代碼,則繼續編譯;如果在指定目錄找不到實作檔案,或者在該檔案及後續的各include檔案中未找到實作代碼,則傳回一個編譯錯誤.其實include的過程完全可以“看成”是一個檔案拼接的過程,将聲明和實作分别寫在頭檔案及C檔案中,或者将二者同時寫在頭檔案中,理論上沒有本質的差別。以上是所謂動态方式。對于靜态方式,基本所有的C/C++編譯器都支援一種連結方式被稱為Static Link,即所謂靜态連結。在這種方式下,我們所要做的,就是寫出包含函數,類等等聲明的頭檔案(a.h,b.h,...),以及他們對應的實作檔案(a.cpp,b.cpp,...),編譯程式會将其編譯為靜态的庫檔案(a.lib,b.lib,...)。在随後的代碼重用過程中,我們隻需要提供相應的頭檔案(.h)和相應的庫檔案(.lib),就可以使用過去的代碼了。相對動态方式而言,靜态方式的好處是實作代碼的隐蔽性,即C++中提倡的“接口對外,實作代碼不可見”。有利于庫檔案的轉發.c檔案和.h檔案的概念與聯系

      如果說難題最難的部分是基本概念,可能很多人都會持反對意見,但實際上也确實如此。我高中的時候學實體,老師抓的重點就是概念——概念一定要搞清,于是難題也成了容易題。如果你能分析清楚一道實體難題存在着幾個實體過程,每一個過程都遵守那一條實體定律(比如動量守恒、牛II定律、能量守恒),那麼就很輕松的根據定律列出這個過程的方程,N個過程必定是N個N元方程,難題也就迎刃而解。即便是高中的實體競賽難題,最難之處也不過在于:

(1)、混淆你的概念,讓你無法分析出幾個實體過程,或某個實體過程遵循的那條實體定律;

(2)、存在高次方程,列出方程也解不出。而後者已經是數學的範疇了,是以說,最難之處還在于掌握清晰的概念;

  程式設計也是如此,如果概念很清晰,那基本上沒什麼難題(會難在數學上,比如算法的選擇、時間空間與效率的取舍、穩定與資源的平衡上)。但是,要掌握清晰的概念也沒那麼容易。比如下面這個例子,看看你有沒有很清晰透徹的認識。

//a.h

void foo();

//a.c

#include "a.h"   //我的問題出來了:這句話是要,還是不要?

void foo()

{

     return;

}

//main.c

#include "a.h"

int main(int argc, char *argv[])

{

    foo();

  return 0;

}               

針對上面的代碼,請回答三個問題:

a.c 中的 #include "a.h" 這句話是不是多餘的?

為什麼經常見 xx.c 裡面 include 對應的 xx.h?

如果 a.c 中不寫,那麼編譯器是不是會自動把 .h 檔案裡面的東西跟同名的 .c 檔案綁定在一起?

(請針對上面3道題仔細考慮10分鐘,莫要着急看下面的解釋。:) 考慮的越多,下面了解的就越深。)

  好了,時間到!請忘掉上面的3道題,以及對這三道題引發出的你的想法,然後再聽我慢慢道來。正确的概念是:從C編譯器角度看,.h和.c皆是浮雲,就是改名為.txt、.doc也沒有大的分别。換句話說,就是.h和.c沒啥必然聯系。.h中一般放的是同名.c檔案中定義的變量、數組、函數的聲明,需要讓.c外部使用的聲明。這個聲明有啥用?隻是讓需要用這些聲明的地方友善引用。因為 #include "xx.h" 這個宏其實際意思就是把目前這一行删掉,把 xx.h 中的内容原封不動的插入在目前行的位置。由于想寫這些函數聲明的地方非常多(每一個調用 xx.c 中函數的地方,都要在使用前聲明一下子),是以用 #include "xx.h" 這個宏就簡化了許多行代碼——讓預處理器自己替換好了。也就是說,xx.h 其實隻是讓需要寫 xx.c 中函數聲明的地方調用(可以少寫幾行字),至于 include 這個 .h 檔案是誰,是 .h 還是 .c,還是與這個 .h 同名的 .c,都沒有任何必然關系。

  這樣你可能會說:啊?那我平時隻想調用 xx.c 中的某個函數,卻 include了 xx.h 檔案,豈不是宏替換後出現了很多無用的聲明?沒錯,确實引入了很多垃圾,但是它卻省了你不少筆墨,并且整個版面也看起來清爽的多。魚與熊掌不可得兼,就是這個道理。反正多些聲明(.h一般隻用來放聲明,而放不定義,參見拙著“過馬路,左右看”)也無害處,又不會影響編譯,何樂而不為呢?

翻回頭再看上面的3個問題,很好解答了吧?

答:不一定。這個例子中顯然是多餘的。但是如果.c中的函數也需要調用同個.c中的其它函數,那麼這個.c往往會include同名的.h,這樣就不需要為聲明和調用順序而發愁了(C語言要求使用之前必須聲明,而include同名.h一般會放在.c的開頭)。有很多工程甚至把這種寫法約定為代碼規範,以規範出清晰的代碼來。

答:1中已經回答過了。

答:不會。問這個問題的人絕對是概念不清,要不就是想混水摸魚。非常讨厭的是中國的很多考試出的都是這種爛題,生怕别人有個清楚的概念了,絕對要把考生搞暈。

搞清楚文法和概念說易也易,說難也難。竅門有三點:

不要暈着頭工作,要抽空多思考思考,多看看書;

看書要看好書,問人要問強人。爛書和爛人都會給你一個錯誤的概念,誤導你;

勤能補拙是良訓,一分辛苦一分才;

(1)通過頭檔案來調用庫功能。在很多場合,源代碼不便(或不準)向使用者公布,隻要向使用者提供頭檔案和二進制的庫即可。使用者隻需要按照頭檔案中的接口聲明來調用庫功能,而不必關心接口怎麼實作的。編譯器會從庫中提取相應的代碼。

(2)頭檔案能加強類型安全檢查。如果某個接口被實作或被使用時,其方式與頭檔案中的聲明不一緻,編譯器就會指出錯誤,這一簡單的規則能大大減輕程式員調試、改錯的負擔。

頭檔案用來存放函數原型。

頭檔案如何來關聯源檔案?

 這個問題實際上是說,已知頭檔案“a.h”聲明了一系列函數(僅有函數原型,沒有函數實作),“b.cpp”中實作了這些函數,那麼如果我想在“c.cpp”中使用“a.h”中聲明的這些在“b.cpp”中實作的函數,通常都是在“c.cpp”中使用#include “a.h”,那麼c.cpp是怎樣找到b.cpp中的實作呢?

其實.cpp和.h檔案名稱沒有任何直接關系,很多編譯器都可以接受其他擴充名。

譚浩強老師的《C程式設計》一書中提到,編譯器預處理時,要對#include指令進行“檔案包含處理”:将headfile.h的全部内容複制到#include “headfile.h”處。這也正說明了,為什麼很多編譯器并不care到底這個檔案的字尾名是什麼----因為#include預處理就是完成了一個“複制并插入代碼”的工作。

程式編譯的時候,并不會去找b.cpp檔案中的函數實作,隻有在link的時候才進行這個工作。我們在b.cpp或c.cpp中用#include “a.h”實際上是引入相關聲明,使得編譯可以通過,程式并不關心實作是在哪裡,是怎麼實作的。源檔案編譯後成生了目标檔案(.o或.obj檔案),目标檔案中,這些函數和變量就視作一個個符号。在link的時候,需要在makefile裡面說明需要連接配接哪個.o或.obj檔案(在這裡是b.cpp生成的.o或.obj檔案),此時,連接配接器會去這個.o或.obj檔案中找在b.cpp中實作的函數,再把他們build到makefile中指定的那個可以執行檔案中。

在VC中,一幫情況下不需要自己寫makefile,隻需要将需要的檔案都包括在project中,VC會自動幫你把makefile寫好。

通常,編譯器會在每個.o或.obj檔案中都去找一下所需要的符号,而不是隻在某個檔案中找或者說找到一個就不找了。是以,如果在幾個不同檔案中實作了同一個函數,或者定義了同一個全局變量,連結的時候就會提示“redefined”

05.如何編寫自己的h檔案和c檔案,并按項目管理多個源檔案-Make

我們在編寫的程式的時候,為了複用會封裝一些方法,這樣就涉及到多個檔案的編譯,如何編譯這些不同的檔案,并連結成最後的可執行程式,如何管理這些檔案,一次編譯?

編寫math.h 頭檔案

extern int add(int i, int j);

編寫math.c檔案

int add(int i, int j)

{

        return i+j;

}

編寫main.c程式

#include "math.h"

#include

int main(int argc, int argv)

{

        printf("the sum 3+5=%d./n",add(3,5));

        return 0;

}

最後,通過 gcc main.o math.o -o main 生成main程式。

[email protected]:~/cpp/make$ cat makefile

main: main.o math.o

gcc main.o math.o -o main

main.o : main.c math.h

gcc -c main.c -o main.o

math.o : math.c math.h

gcc -c math.c -o math.o

clean:

rm -f *.o

[email protected]:~/cpp/make$

一個簡單的問題:.c和.h檔案的差別

學了幾個月的C語言,反而覺得越來越不懂了。同樣是子程式,可以定義在.c檔案中,也可以定義在.h檔案中,那這兩個檔案到底在用法上有什麼差別呢?

2樓:

子程式不要定義在.h中。

函數定義要放在.c中,而.h隻做聲明.否則多引用幾次,就會發生函數重複定義的錯誤。

3樓:

.h隻做聲明,編譯後不産生代碼

4樓:

這樣做目的是為了實作軟體的子產品化

使軟體結構清晰,而且也便于别人使用你寫的程式

純粹用 C 語言文法的角度,你當然可以在 .h 中放任何東西,因為 #include 完全等價 于把 .h 檔案 Ctrl-C Ctrl-V 到 .c 中

.h 中應該都是一些宏定義和變量、函數聲明,告訴别人你的程式“能幹什麼、該怎麼用”

.c 中是所有變量和函數的定義,告訴計算機你的程式“該怎麼實作”

5樓:

當然,如果一個 .h 被多個 .c 包含

而且 .h 中有對象(變量或函數)的定義,就會發生重複定義的錯誤了

聲明可以無窮多次,定義隻能一次

6樓:

一般來說,一個C檔案應該是一個子產品

如果你的程式僅僅有一個子產品(僅僅一個C檔案),就可以不用建立H檔案了。

否則你的子產品肯定不是獨立的,你的子產品裡面的實作要被别的子產品調用。這個時候你最好生成一個頭檔案(H檔案),在頭檔案裡面可以聲明你的那些函數是公共的。當别的子產品包含你的頭檔案後,就可以使用你的公共聲明了。

7樓:

一個C對應一個H,這樣管理起來友善

比如你有一個"feed_dog.c",那麼就再添加一個"feed_dog.h":

#ifndef _feed_dog_h

#define _feed_dog_h

extern void feed_dog(void);

#endif

其實在H檔案裡寫函數也無所謂,隻是不符合習慣而已。隻要按照以上的格式寫,一個H檔案添加多少次都無所謂,呵呵

8樓:

隻是一種約定

    在編譯器裡面,.c和.h是沒有差別的,.c和.h如何使用完全取決于程式員,不過為了你的程式以後還能看懂而且别人也能看懂,請遵守普遍的約定,這些約定前面的大蝦們已經講了很多了.

    這個就象汽車在馬路上要靠右行使一樣,是人為約定,汽車(編譯器)本身并不知道自己是在靠左還是靠右行使.

    如果你喜歡,還可以用任意字尾命名源檔案和頭檔案,但這樣幹可能會導緻內建編譯和調試環境罷工,你隻好自己寫makefile檔案了.

9樓:

非常感謝各位大俠,不過我現在越來越糊塗了

1,當一個函數要經常使用(比如有十幾個C檔案使用它)時,一般我都放在H檔案裡,并在前面加上__inline.對于__inline函數,很多C檔案都可以INCLUDE這個H檔案,但是它好象隻能被一個H檔案INCLUDE,如果有兩個H檔案INCLUDE它,就會出現編譯錯誤。

2,有些數組變量,其大小可能達十幾K,而且要賦初值,這就不放在C檔案裡了,要不人都蒙了。

3,

#ifndef _feed_dog_h

#define _feed_dog_h

extern void feed_dog(void);

#endif

mohanwei兄,是不是這樣定議了,這個feed_dog.h就可以無數次的被INCLUDE了?

11樓:

#ifndef _feed_dog_h //如果到目前為止還沒有定義過“_feed_dog_h”這個宏

#define _feed_dog_h //則定義“_feed_dog_h”這個宏

extern void feed_dog(void); //聲明一個外部函數

#endif //“#ifndef”到此結束

是以,不管你定義多少次(哪怕你在同一個C檔案裡定義多次),都不會發生沖突的。

在網上看到一篇關于.H和.C的文章,感覺不錯,帖出與大家共享.

簡單的說

其實要了解C檔案與頭檔案有什麼不同之處,首先需要弄明白編譯器的工作過程,一般說來編譯器會做以下幾個過程:

1.預處理階段

2.詞法與文法分析階段

3.編譯階段,首先編譯成純彙編語句,再将之彙編成跟CPU相關的二進制碼,生成各個目标檔案

4.連接配接階段,将各個目标檔案中的各段代碼進行絕對位址定位,生成跟特定平台相關的可執行檔案,當然,最後還可以用objcopy生成純二進制

碼,也就是去掉了檔案格式資訊.

編譯器在編譯時是以C檔案為機關進行的,也就是說如果你的項目中一個C檔案都沒有,那麼你的項目将無法編譯,連接配接器是以目标檔案為機關

,它将一個或多個目标檔案進行函數與變量的重定位,生成最終的可執行檔案,在PC上的程式開發,一般都有一個main函數,這是各個編譯器

的約定,當然,你如果自己寫連接配接器腳本的話,可以不用main函數作為程式入口!!!!

有了這些基礎知識,再言歸正傳,為了生成一個最終的可執行檔案,就需要一些目标檔案,也就是需要C檔案,而這些C檔案中又需要一個main

函數作為可執行程式的入口,那麼我們就從一個C檔案入手,假定這個C檔案内容如下:

#include

#include "mytest.h"

int main(int argc,char **argv)

{

 test = 25;

 printf("test.................%d/n",test);

}

頭檔案内容如下:

int test;

現在以這個例子來講解編譯器的工作:

1.預處理階段:編譯器以C檔案作為一個單元,首先讀這個C檔案,發現第一句與第二句是包含一個頭檔案,就會在所有搜尋路徑中尋找這兩個

檔案,找到之後,就會将相應頭檔案中再去處理宏,變量,函數聲明,嵌套的頭檔案包含等,檢測依賴關系,進行宏替換,看是否有重複定義

與聲明的情況發生,最後将那些檔案中所有的東東全部掃描進這個目前的C檔案中,形成一個中間“C檔案”

2.編譯階段,在上一步中相當于将那個頭檔案中的test變量掃描進了一個中間C檔案,那麼test變量就變成了這個檔案中的一個全局變量,此時

就将所有這個中間C檔案的所有變量,函數配置設定空間,将各個函數編譯成二進制碼,按照特定目标檔案格式生成目标檔案,在這種格式的目标文

件中進行各個全局變量,函數的符号描述,将這些二進制碼按照一定的标準組織成一個目标檔案

3.連接配接階段,将上一步成生的各個目标檔案,根據一些參數,連接配接生成最終的可執行檔案,主要的工作就是重定位各個目标檔案的函數,變量

等,相當于将個目标檔案中的二進制碼按一定的規範合到一個檔案中

再回到C檔案與頭檔案各寫什麼内容的話題上:

理論上來說C檔案與頭檔案裡的内容,隻要是C語言所支援的,無論寫什麼都可以的,比如你在頭檔案中寫函數體,隻要在任何一個C檔案包含此

頭檔案就可以将這個函數編譯成目标檔案的一部分(編譯是以C檔案為機關的,如果不在任何C檔案中包含此頭檔案的話,這段代碼就形同虛設

),你可以在C檔案中進行函數聲明,變量聲明,結構體聲明,這也不成問題!!!那為何一定要分成頭檔案與C檔案呢?又為何一般都在頭件

中進行函數,變量聲明,宏聲明,結構體聲明呢?而在C檔案中去進行變量定義,函數實作呢??原因如下:

1.如果在頭檔案中實作一個函數體,那麼如果在多個C檔案中引用它,而且又同時編譯多個C檔案,将其生成的目标檔案連接配接成一個可執行檔案

,在每個引用此頭檔案的C檔案所生成的目标檔案中,都有一份這個函數的代碼,如果這段函數又沒有定義成局部函數,那麼在連接配接時,就會發

現多個相同的函數,就會報錯

2.如果在頭檔案中定義全局變量,并且将此全局變量賦初值,那麼在多個引用此頭檔案的C檔案中同樣存在相同變量名的拷貝,關鍵是此變量被

賦了初值,是以編譯器就會将此變量放入DATA段,最終在連接配接階段,會在DATA段中存在多個相同的變量,它無法将這些變量統一成一個變量,也就是僅為此變量配置設定一個空間,而不是多份空間,假定這個變量在頭檔案沒有賦初值,編譯器就會将之放入BSS段,連接配接器會對BSS段的多個

同名變量僅配置設定一個存儲空間

3.如果在C檔案中聲明宏,結構體,函數等,那麼我要在另一個C檔案中引用相應的宏,結構體,就必須再做一次重複的工作,如果我改了一個C

檔案中的一個聲明,那麼又忘了改其它C檔案中的聲明,這不就出了大問題了,程式的邏輯就變成了你不可想象的了,如果把這些公共的東東放

在一個頭檔案中,想用它的C檔案就隻需要引用一個就OK了!!!這樣豈不友善,要改某個聲明的時候,隻需要動一下頭檔案就行了

4.在頭檔案中聲明結構體,函數等,當你需要将你的代碼封裝成一個庫,讓别人來用你的代碼,你又不想公布源碼,那麼人家如何利用你的庫

呢?也就是如何利用你的庫中的各個函數呢??一種方法是公布源碼,别人想怎麼用就怎麼用,另一種是提供頭檔案,别人從頭檔案中看你的

函數原型,這樣人家才知道如何調用你寫的函數,就如同你調用printf函數一樣,裡面的參數是怎樣的??你是怎麼知道的??還不是看人家

的頭檔案中的相關聲明啊!!!當然這些東東都成了C标準,就算不看人家的頭檔案,你一樣可以知道怎麼使用

程式源碼中".h"檔案與".c"檔案有什麼差別呀??

在一個程式源碼中,看到了udp.h檔案又看到了udp.c檔案,不知道這兩者是什麼關系呀?又有何差別呢?哪位高手前來幫忙,謝謝謝謝.

 一級最佳答案.c就是C語言系列的源檔案,以文本形式存在,而.h系列則是頭檔案,即C系列中存放函數和全局變量的檔案,因為C中的函數是被封裝起來的,即無法看到其代碼.

 頭檔案與之實作檔案的的關系

今天在網上看到一篇解釋.h與.c(.cpp)的文章,我讀完後感到有些地方不妥,特此按照我的了解,給初學者一些指導~

說幾句題外話,剛才讓女朋友陪我出去一會,她說她要先化化妝,我随口就來--簡單就是美麗啊!

你猜她說什麼:美麗除了天生麗質外,保養也是很重要的~ 我倒~

你了解簡單的含義嗎?

關于兩者以前的關系,要從N年以前說起了~ long long ago,once aupon a time .......

那是一個被遺忘的年代,在編譯器隻認識.c(.cpp))檔案,而不知道.h是何物的年代。

那時的人們寫了很多的.c(.cpp)檔案,漸漸地,人們發現在很多.c(.cpp)檔案中的聲明語句就是相同的,但他們卻不得不一個字一個字地重複地将這些内容敲入每個.c(.cpp)檔案。但更為恐怖的是,當其中一個聲明有變更時,就需要檢查所有的.c(.cpp)檔案,并修改其中的聲明,啊~簡直是世界末日降臨!

終于,有人(或許是一些人)再不能忍受這樣的折磨,他(們)将重複的部分提取出來,放在一個新檔案裡,然後在需要的.c(.cpp)檔案中敲入#include   XXXX這樣的語句。這樣即使某個聲明發生了變更,也再不需要到處尋找與修改了---世界還是那麼美好!

因為這個新檔案,經常被放在.c(.cpp)檔案的頭部,是以就給它起名叫做“頭檔案”,擴充名是.h.

從此,編譯器(其實是預處理器)就知道世上除了.c(.cpp)檔案,還有個.h的檔案,以及一個叫做#include指令。

雖然後來又發生很多的變化,但是這樣的用法一直延續至今,隻是時日久遠了,人們便淡忘了當年的緣由罷了。

提到了頭檔案,就說說它的作用吧~

想到了林銳GG寫的高品質C/C++程式設計上頭檔案的作用的簡短描述:

(1)通過頭檔案來調用庫功能。在很多場合,源代碼不便(或不準)向使用者公布,隻要向使用者提供頭檔案和二進制的庫即可。使用者隻需要按照頭檔案中的接口聲明來調用庫功能,而不必關心接口怎麼實作的。編譯器會從庫中提取相應的代碼。

(2)頭檔案能加強類型安全檢查。如果某個接口被實作或被使用時,其方式與頭檔案中的聲明不一緻,編譯器就會指出錯誤,這一簡單的規則能大大減輕程式員調試、改錯的負擔。

預處理是編譯器的前驅,作用是把存儲在不同檔案裡的程式子產品內建為一個完整的源程式.

#include本身隻是一個簡單的檔案包含預處理指令,即為把include的後面檔案放到這條指令這裡,除此之外,沒有其它的用處(至少我也樣認為).

我對乾坤一笑兄的觀點,十分贊同,基礎的東東一定要弄明白.

我下面就乾坤一笑兄的例子做講,完備他的一些讓人迷惑不解的時候~

例子:

//a.h

void foo();

//a.c

#include "a.h"  //我的問題出來了:這句話是要,還是不要?

void foo()

{

    return;

}

//main.c

#include "a.h"

int main(int argc, char *argv[])

{

   foo();

  return 0;

}

針對上面的代碼,請回答三個問題:

a.c 中的 #include "a.h" 這句話是不是多餘的?

1.為什麼經常見 xx.c 裡面 include 對應的 xx.h?

2.如果 a.c 中不寫,那麼編譯器是不是會自動把 .h 檔案裡面的東西跟同名的 .c 檔案綁定在一起?

3.第三個問題我給他改了一下:如果 a.c 中不寫include<>,那麼編譯器是不是會自動把 .h 檔案裡面的東西跟同名的.c檔案綁定在一起?

下面是乾坤一笑的原話:

從C編譯器角度看,.h和.c皆是浮雲,就是改名為.txt、.doc也沒有大的分别。換句話說,就是.h和.c沒啥必然聯系。.h中一般放的是同名.c檔案中定義的變量、數組、函數的聲明,需要讓.c外部使用的聲明。這個聲明有啥用?隻是讓需要用這些聲明的地方友善引用。因為 #include "xx.h" 這個宏其實際意思就是把目前這一行删掉,把 xx.h 中的内容原封不動的插入在目前行的位置。由于想寫這些函數聲明的地方非常多(每一個調用 xx.c 中函數的地方,都要在使用前聲明一下子),是以用 #include "xx.h" 這個宏就簡化了許多行代碼——讓預處理器自己替換好了。也就是說,xx.h 其實隻是讓需要寫 xx.c 中函數聲明的地方調用(可以少寫幾行字),至于 include 這個 .h 檔案是誰,是 .h 還是 .c,還是與這個 .h 同名的 .c,都沒有任何必然關系。

    這樣你可能會說:啊?那我平時隻想調用 xx.c 中的某個函數,卻 include了 xx.h 檔案,豈不是宏替換後出現了很多無用的聲明?沒錯,确實引入了很多垃圾 ,但是它卻省了你不少筆墨,并且整個版面也看起來清爽的多。魚與熊掌不可得兼,就是這個道理。反正多些聲明(.h一般隻用來放聲明,而放不定義,參見拙著“過馬路,左右看”)也無害處,又不會影響編譯,何樂而不為呢?

翻回頭再看上面的3個問題,很好解答了吧?

它的解答如下:

答:1.不一定。這個例子中顯然是多餘的。但是如果.c中的函數也需要調用同個.c中的其它函數,那麼這個.c往往會include同名的.h,這樣就不需要為聲明和調用順序而發愁了(C語言要求使用之前必須聲明,而include同名.h一般會放在.c的開頭)。有很多工程甚至把這種寫法約定為代碼規範,以規範出清晰的代碼來。

2.答:1中已經回答過了。

3.答:不會。問這個問題的人絕對是概念不清,要不就是想混水摸魚。非常讨厭的是中國的很多考試出的都是這種爛題,生怕别人有個清楚的概念了,絕對要把考生搞暈。

over!

在此裡要明确一點,編譯器是按照編譯單元進行編譯的,所謂的編譯單元,是指一個.c檔案以及它所include的所有.h檔案.最直覺的了解就是一個檔案,一個工程中可以包含很多檔案,其中有一個程式的入口點,即我們通常所說的main()函數(當然也可以沒有這個函數,程式照樣能啟動,詳細見我的blog中).在沒有這個程式入口點的情況下,編譯單元隻生成目标檔案object file(.o檔案,windows下叫做.obj).

這個例子中總共包含了二個編譯單元,分别是a.c,main.c,按照我所說的,在編譯階段隻是生成各自的.o檔案.這個階段不和其它的檔案發生任何的關系.

而include這個預處理指令發生在預處理階段(早先編譯階段,隻是編譯器的一個前驅處理程式).

.h .c不見得是浮雲,脫離了編譯器談這些沒有任何的意義,抛開更深層次的這些,比如說,OS如何啟動這個檔案,PE結構(linux 下為elf)等等

編譯器首先要識别這個檔案才可能去編譯它,這是前提.如果你改了它的擴充名那麼你的編譯器還能認識它嗎~上升到一個更高的層次上看待這個問題,XX兄說的也不錯~我想XX兄說的意思就是兩者不可因為名字相同就認為兩者有什麼關系,名字是可以随便的~

兩者之間的聯系,我在前面說過了,是由于曆史的原因造成的,再加上人的習慣,我想誰也不想多去記那麼多檔案名吧.(拿我舉個例子,一個數

據表如果多于30個字段,我就覺得頭大了,現在弄的表有的多達上百個字段,真希望那位高人研究出什麼好的方法來~,也讓我們的世界美好一些~)

乾坤一笑的第三個問題很有代表性,多次在網上看到,現在的編譯器絕對沒有那麼智能,而且也沒有必須那麼做.下面我們主要聊聊編譯器的處理過程.(我想初學者有疑問的正在于此,即是對于編譯過程.h .c(.cpp)的變化不太了解,)

下面我說舉個簡單的例子來聊聊~

例子如下:

//a.h

class   A

{

pubic:

      int   f(int   t);

};

//a.cpp

#include   "a.h"

int   A::f(int   t)

{

    return   t;

}

//main.cpp

#include   "a.h"

void   main()

{

      A   a;

      a.f(3);

}

在預處理階段,預處理器看到#include "檔案名"就把這個檔案讀進來,比如它編譯main.cpp,看到#include   "a.h",它就把a.h的内容讀進來,它知道了,有一類A,包含一個成員函數f,這個函數接受一個int型的參數,傳回一個int型的值。再往下編譯很容易就把A   a這行讀懂了,它知道是要拿A這個類在棧上生成一個對象。再往下,它知道了下面要調用A的成員函數f了,參數是3,由于它知道這個函數要一個整形數用參數,這個3正好比對,那就正好把它放到棧上,生成一條調用f(int)函數的指令(一般可能是一句call),至于這個f(int)函數到底在哪裡,它不知道,它留着空,連結時再解決。它還知道f(int)函數要傳回一個int,是以也許它也為這一點做好了準備(在例子中,我們沒用這個傳回值,也許它就不處理)。再往下到檔案末尾了main.cpp編譯好了,生成了main.obj。整個編譯過程中根本就不需要知道a.cpp的内容。

同理,編譯器再編譯a.cpp,把f()函數編譯好,編譯a.cpp時,它也不用管别的,把f()編譯好就行了。生成了a.obj。

最後一步就是連結的階段了,連結器把項目中所有.cpp生成的所有.obj連結起來,

在這一步中,它就明确了f(int)函數的實作所在的位址,把main.obj中空着的這個位址位置填上正确的位址。最終生成了可執行檔案main.exe。

明白了嗎?不明白那就多說幾句了,我們在學編譯原理的時候都知道,編譯器是分階段進行的,每一個階段将源程式從一種表示轉換成另一種表示,一般情況下都進行如下順序:源程式->詞法分器->文法分析器->語義分析器->中間代碼生成器->代碼優化器->代碼生成器->目标程式.

其中這中間6項活動都要涉及的兩項主要活動是:符号管理器與錯誤處理器.

歸根原因,這裡有一個叫做符号表的東東在裡面讓你着魔一樣不明白,其實符号表是一個資料結構.編譯器的基本一項功能就是要記錄源程式中使用的辨別符并收集與每個辨別符相關的各種屬性資訊.屬性資訊表明了該辨別符的存儲位置/類型/作用域(在那個階段有效)等資訊,通俗的說一下就是,當編譯器看到一個符号聲明時,例如你的函數名它就會把它放到這個符号表中去登記一下~符号表裡存放着你的函數的入口位址,參數個數,傳回資訊等等一堆東西~而在聯接階段主要是處理工程中的符号表與調用對應處理關系,即我們通常所說的解引用.

經過前面的,不知明白與否?

最後引用一下XXX兄的結尾三點:

搞清楚文法和概念說易也易,說難也難。竅門有三點:

1.不要暈着頭工作,要抽空多思考思考,多看看書;

2.看書要看好書,問人要問強人。爛書和爛人都會給你一個錯誤的概念,誤導你;

3.勤能補拙是良訓,一分辛苦一分才;

如果認為.c和.h檔案是僅僅名字不一樣難免了解得膚淺了點.有op的曆史看來,語言的發展是趨向與oop..h檔案的出現.有點類的性質在裡邊..h檔案的隐蔽性好.這個道理不難發現.隻要大開c自己的.h檔案看看,就很明顯了.是以,我同意XXX兄認為乾坤一笑的膚淺.

但是,從另外一個方面看.:

(至于編譯器的實作.我還沒了解.不過.我相信.象)

//a.cpp

#include "a.h"

int A::f(int t)

{

return t;

}

這樣的程式不會出現吧....呵呵.是以現在的人要了解.h和.c簡單化.也有點曆史和時代的影響.

小弟愚鈍.看了幾次終于是看懂了.

現在總結一下:(有不對的請pk)

1.頭檔案可以預先告訴編譯器一些必要的聲明,讓編譯器順利進行下去,在連接配接實作以前.未必出現實際的定義.

頭檔案的意義在

a.使得程式簡明,清晰.

b.避免了重複編寫相同的聲明代碼.

2.**.c和**.h檔案沒有必然的聯系

本文來自CSDN部落格,轉載請标明出處:http://blog.csdn.net/shi_869160/archive/2010/07/03/5710441.aspx

繼續閱讀