好吧,頭文件是干嘛的?
放心,這個簡單的問題確實把我難住了。用了多少年頭文件,頭文件是干嘛用的?
第一,頭文件是給別人看得。
第二,頭文件可以用來進行類型檢查,減少出錯。
好吧,第二點我似懂非懂,所以只說第一點。再配合extern。他們倆在一起很萬惡的。真的哦!
頭文件是給別人看的。不管是C還是C++,你把你的函數,變量或者結構體,類啥的放在你的.c或者.cpp文件里。
然后編譯成lib,dll,obj,.o等等,然后別人用的時候 最基本的gcc hisfile.cpp yourfile.o|obj|dll|lib 等等。
ok,他們怎么知道你的lib,dll...里面到底有什么東西?要看你的頭文件。你的頭文件就是對用戶的說明。函數,參數,各種各樣的接口的說明。
so,既然是說明,那么頭文件里面放的自然就是關於函數,變量,類的“聲明”了。記着,是“聲明”,不是“定義”。
那么,我假設大家知道聲明和定義的區別。所以,最好不要傻嘻嘻的在頭文件里定義什么東西。比如全局變量:
#ifndef _XX_頭文件.H
#define _XX_頭文件.H
int A;
#endif
那么,很糟糕的是,這里的int A是個全局變量的定義,所以如果這個頭文件被多次引用的話,你的A會被重復定義
顯然語法上錯了。只不過有了這個#ifndef的條件編譯,所以能保證你的頭文件只被引用一次,不過也許還是會岔子,所以,最好不要在頭文件里定義太多東西。
那么
ok
extern int a;//聲明一個全局變量a
int a; //定義一個全局變量a
extern int a =0 ;//定義一個全局變量a 並給初值。
int a =0;//定義一個全局變量a,並給初值,
第四個 等於 第 三個,都是定義一個可以被外部使用的全局變量,並給初值。
糊塗了吧,他們看上去可真像。但是定義只能出現在一處。也就是說,不管是int a;還是extern int a=0;還是int a=0;都只能出現一次,而那個extern int a可以出現很多次。當你要引用一個全局變量的時候,你就要聲明,extern int a;這時候extern不能省略,因為省略了,就變成int a;這是一個定義,不是聲明。
函數,函數,對於函數也一樣,也是定義和聲明,定義的時候用extern,說明這個函數是可以被外部引用的,聲明的時候用extern說明這是一個聲明。
但是對於函數extern,不管是聲明還是定義都是可以省略的,因為定義函數要有函數體,聲明函數沒有函數體,兩者如此不同,所以省略了extern也不會有問題。
比如:
int fun(void)
{
return 0;
}
很好,我們定義了一個全局函數
int fun(void);
我們對它做了個聲明,然后后面就可以用了
加不加extern都一樣
我們也可以把對fun的聲明 放在一個頭文件里,最后變成這樣
int fun(void);//函數聲明,所以省略了extern,完整些是extern int fun(void);
int fun(void)
{
return 0;
}//一個完整的全局函數定義,因為有函數體,extern同樣被省略了。
然后,一個客戶,一個要使用你的fun的客戶,把這個頭文件包含進去,ok,一個全局的聲明。沒有問題。
但是,對應的,如果是這個客戶要使用全局變量,那么要extern 某某變量;不然就成了定義了。
很多情況下,程序員可能突然產生這樣的問題:為什么非得在文件的首位置#include "... .h"? 我include源文件行不行。
其實任何后綴的文本文件(忽視編碼情況)都可以被#include,但是你去#include 源文件會不會出問題呢?我們看下面測試代碼:
VC2008下編譯,出現如下問題:
為什么會出現這樣的情況呢?
其實C語言的編譯方式是分離式的,分為兩步(簡單起見我們假設只有一個生成目標):
1、將一個或多個源文件編譯成可重定位的目標文件,其中每個文件是分別編譯的。
2、將1步生成的可重定位目標文件鏈接成一個可執行目標文件或者共享目標文件。
因
此,假設foo1.c中用include指令包含了foo2.c,foo2.c中定義了函數function且沒有用static修飾。那么經過預處
理,foo1.c中也將包含function的定義。於是foo1.c生成的可重定位目標文件(假設為foo1.o)和foo2.c(假設為
foo2.o)都有function這個符號,這樣上面所說的第二步就會因為沖突而失敗。
事實上,在沒有頭文件可被包含的情況下(事實上初期,是最早最早的時候),我們可以采用如下的方式:
程序編譯運行,是我們想要的結果。其實這個就是一個前置聲明。事實上,我們不加"extern"關鍵字,編譯也正常:
這是因為:函數具有定義和聲明,定義的時候用extern,說明這個函數是可以被外部引用的,聲明的時候用extern說明這是一個聲明。但由於函
數的定義和聲明是有區別的,定義函數要有函數體,聲明函數沒有函數體,所以函數定義和聲明時都可以將extern省略掉,反正其他文件也是知道這個函數是
在其他地方定義的,所以不加extern也行。兩者如此不同,所以省略了extern也不會有問題。
我們再看變量的聲明和定義:
extern int a; //聲明一個全局變量a
int a; //定義一個全局變量a
extern int a =0 ; //定義一個全局變量a 並給初值。
int a =0; //定義一個全局變量a,並給初值,
第四個等於第三個,都是定義一個可以被外部使用的全局變量,並給初值。但是定義只能出現在一處。也就是說,不管是int a;還是extern int a=0;還是int a=0;都只能出現一次,而那個extern int a可以出現很多次。當你要引用一個全局變量的時候,你就要聲明,extern int a;這時候extern不能省略,因為省略了,就變成int a;這是一個定義,不是聲明。
因此,訪問者模塊時仍要對變量進行聲明,而這時候是必須要加"extern"關鍵字的。
這樣我們出現一個問題?既然extern+前置聲明都能實現這些功能?為什么還得將聲明放在頭文件中並被包含呢?我覺得下面這段話說得很清楚:
在C語言家族程序中,頭文件被大量使用。一般而言,每個C++/C程序通常由頭文件(header files)和定義文件(definition
files)組成。頭文件作為一種包含功能函數、數據接口聲明的載體文件,用於保存程序的聲明(declaration),而定義文件用於保存程序的實現
(implementation)。
C++/C程序的頭文件以“.h”為后綴。以下是假設名稱為 graphics.h的頭文件:
#ifndef GRAPHICS_H (作用:防止graphics.h被重復引用)
#define GRAPHICS_H
#include.... (作用:引用標准庫的頭文件)
...
#include... (作用:引用非標准庫的頭文件)
...
void Function1(...); (作用:全局函數聲明)
...
class Box (作用:類結構聲明)
{
...
};
#endif
從以上例子可以看出,頭文件一般由三部分內容組成:(1)頭文件開頭處的版權和版本聲明;(2)預處理塊;(3)函數和類結構聲明等。在頭文件中,用
ifndef/define/endif結構產生預處理塊,用 #include
格式來引用庫的頭文件。頭文件的這種結構,是利用C語言進行開發軟件所通常具備的,屬於公有知識。
一般在一個應用開發體系中,功能的真正邏輯
實現是以硬件層為基礎,在驅動程序、功能層程序以及用戶的應用程序中完成的。根據以上示例,可以發現頭文件的主要作用在於調用庫功能,對各個被調用函數給
出一個描述,其本身不包含程序的邏輯實現代碼,它只起描述性作用,告訴應用程序通過相應途徑尋找相應功能函數的真正邏輯實現代碼。用戶程序只需要按照頭文
件中的接口聲明來調用庫功能,編譯器會從庫中提取相應的代碼。
從以上結構圖來看,頭文件是用戶應用程序和函數庫之間的橋梁和紐帶。在整個軟件
中,頭文件不是最重要的部分,但它是C語言家族中不可缺少的組成部分。做一個不算很恰當的比喻,頭文件就像是一本書中的目錄,讀者(用戶程序)通過目錄,
可以很方便就查閱其需要的內容(函數庫)。在一本書中,目錄固然重要,但絕對不是一本書的核心的、最重要的部分。
你既然寫出了一個模塊並且要提供給其它模塊使用的功能,你總不能讓其它模塊去“猜”你模塊的哪些接口吧。所以這時候干脆再帶個索引一樣的頭文件。當然如果你只提供了索引不提供內容,則會發生編譯時沒問題,鏈接時出問題。