1. 程式人生 > >.c和.h檔案的區別

.c和.h檔案的區別

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

 

 一個簡單的問題:.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 <stdio.h>
#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檔案沒有必然的聯絡.

           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述