我們的程式為什麼需要標頭檔案

2021-10-01 08:10:10 字數 4871 閱讀 7758

什麼是原始檔、什麼是標頭檔案

我所見到的最好的答案:

通常,在乙個 c++ 程式中,只包含兩類檔案―― .cpp 檔案和 .h 檔案。其中,.cpp 檔案被稱作 c++ 原始檔,裡面放的都是 c++ 的源**;而 .h 檔案則被稱作 c++ 標頭檔案,裡面放的也是 c++ 的源**。

個人理解:

我們為乙個工程或專案所寫的所有程式檔案都可以被稱為原始檔。其中原始檔包括標頭檔案(c語言的.**件 \ c++的.h或.hpp檔案)和其他檔案(c語言的.c檔案 \ c++的.cpp檔案)。標頭檔案又包括系統標頭檔案與自定義標頭檔案。標頭檔案被其他檔案通過#include來引用。

引用系統標頭檔案是通過 #include < >,表示從特定目錄下開始尋找(linux是目錄 /usr/include),引用自定義標頭檔案是通過 #include " ",表示先從當前目錄下開始尋找,找不到時再從特定目錄下開始尋找。

為什麼需要標頭檔案

從主函式說起

我們所寫的乙個程式中必須要有乙個主函式 main,來表明函式開始的地方。當我們所實現的功能較少時,往往乙個主函式就夠了。隨著我們實現功能的增加,只在主函式中實現使得主函式變得冗長且複雜,不利於後期的除錯和維護,這個時候就需要子函式了。我們可以把一部分功能用子函式來實現,在主函式中呼叫子函式。當子函式因為實現的功能較多變得冗長且複雜時,可以把一部分功能用子函式來實現,稱之為子函式的子函式。因此程式變得利於除錯和維護。

下面的 test1.c 程式展現了這一過程:

// test1.c

intfun (

int num)

intmain (

void

)

我們還可以把子函式 fun 的實現放在 main 函式的後面並且在 mian 函式之前加上 fun 函式的宣告。

// test1.c

int fun (

int num)

;int

main (

void

)int

fun (

int num)

這樣在編譯的時候,當解析到 main 函式中的 fun 函式呼叫時,編譯程式就會向前尋找 fun 函式的實現或宣告,當發現 fun 函式的宣告時,編譯程式就會知道 fun 函式的實現在主函式的之後,編譯程式便會繼續正常編譯。

在一些編譯器編譯程式時,就算在 main 函式之後實現 fun 函式,main 函式之前不加 fun 函式的宣告,也不會有出錯資訊,但正確的語法是要求加上的。

引入標頭檔案

上面的程式結構安排使人感覺即利於修改又便於維護,但仔細一分析還是發現乙個問題。

問題:當我寫了乙個 test2.c 程式檔案,裡面的 main 函式也需要呼叫 fun 函式時,我需要在 test2.c 程式檔案在加入 fun 函式的實現。當我有許多 test 程式檔案,裡面的 main 函式都需要呼叫 fun 函式時,我需要在每乙個 test 程式檔案中都加入 fun 函式的實現。這樣的工作重複而又無意義。

解決這個問題最簡單的方法就是引入標頭檔案。我可以寫乙個 test.h 的自定義標頭檔案,把 fun 函式的實現放進去,每乙個呼叫 fun 函式的 test 程式檔案只需要引入標頭檔案 test.h 即可。

這裡需要解釋一下編譯第一階段——預處理。預處理器(cpp)根據以位元組#開頭的命令,修改原始的c程式。比如 test.c 中第1行的 #include 「test.h」 命令告訴預處理器讀取自定義標頭檔案 test.h 的內容,並把它直接插入到程式文字中。結果就得到了另乙個c程式,通常是以 .i 作為副檔名。這裡所說的插入是直接把整個系統標頭檔案copy到程式文字#include的位置。並且加上一些標誌資訊。

下面**展示:

// test.h

#ifndef test_h

#define test_h

intfun (

int num)

#endif

// test.c

#include

"test.h"

intmain (

void

)

經過預處理後,得到 test.i 程式檔案。

// test.i

# 1"test.c"

# 1""

# 1""

# 31

""# 1

"/usr/include/stdc-predef.h"13

4# 32""2

# 1"test.c"

# 1"test.h"

1int

fun (

int num)

# 2"test.c"

2int

main (

void

)

可以發現 test.h 自定義標頭檔案被複製到了原先 #include 的位置,並且加上了一些標誌資訊。因為我們的 #include 在主函式之前,所以在 test.i 程式檔案中,fun 函式的實現在 main 函式的前面,因此我們不需要任何函式宣告。

引入fun.c檔案

這樣做很快就會發現另乙個問題。

問題:當我因為需要修改 main 函式後,在重新編譯的過程中,因為 test.c 檔案中有 #include 「test.h」,預處理後得到 test.i 預處理檔案,fun 函式的實現被複製到 test.i 檔案中,fun 函式也需要重新編譯一遍。當 fun 函式的實現較為簡單時,這樣做沒有太大的影響,但是當 fun 函式的實現較為複雜時,這樣做十分不利於除錯和維護。

解決這個問題最好的方法就是引入 fun.c 檔案,把 fun 函式的實現放在 fun.c 檔案中,在 test.h 自定義標頭檔案中放入 fun 函式的宣告。**如下:

// fun.c

intfun (

int num)

// test.h

#ifndef test_h

#define test_h

extern

int fun (

int num)

;#endif

// test.c

#include

"test.h"

intmain (

void

)

這樣做我們可以對 test.c 和 fun.c 這兩個檔案分開預處理、編譯、彙編。當得到 fun.o 和 test.o 兩個可重定位目標檔案時,用鏈結器將這兩個檔案連線成可執行目標檔案 test 。

gcc -e test.c -o test.i  // 預處理

gcc -s test.i -o test.s // 編譯

gcc -c test.s -o test.o // 彙編

gcc -e fun.c -o fun.i // 預處理

gcc -s fun.i -o fun.s // 編譯

gcc -c fun.s -o fun.o // 彙編

gcc fun.o test.o -o test // 鏈結

當我們需要修改 main 函式時,只需要對 test.c 檔案重新預處理、編譯、彙編。得到 fun.o 後重新與 test.o 鏈結一下就可以得到可執行目標檔案 test 。

因為 main 函式中呼叫了 fun 函式,而 fun 函式的實現又沒有與 main 函式在同一檔案中,所以需要在 test.c 檔案中 main 函式之前加上 fun 函式的外部宣告,以此來表明 fun 函式的實現在另乙個檔案中。而 fun 函式的宣告在自定義標頭檔案 test.h 中,所以在 test.c 檔案中 mian 函式之前加上 #include 「test.h」 。

檢視預處理 test.c 後得到的 test.i 檔案可以檢驗這個觀點。

// test.i    

# 1"test.c"

# 1""

# 1""

# 31

""# 1

"/usr/include/stdc-predef.h"13

4# 32""2

# 1"test.c"

# 1"test.h"

1extern

int fun (

int num)

;# 2

"test.c"

2int

main (

void

)

如果在 test.c 檔案中沒有 #include 「test.h」 ,在編譯過程中便會出現警告資訊:warning : implicit declaration of function 『fun』

聯絡系統標頭檔案

如果檢視過系統標頭檔案,便可以發現:在系統標頭檔案中都是一些函式宣告和巨集定義,而沒有任何函式的實現。我們呼叫庫函式時必須要包含特定的系統標頭檔案,就是因為系統標頭檔案中有我們使用庫函式的外部宣告。

拿 hello.c 程式檔案來舉例,**如下:

#include

intmain()

因為我們的程式檔案中呼叫了庫函式 printf ,所以需要包含標頭檔案 #include ,因為系統標頭檔案 stdio.h 中有 printf 函式的宣告,預處理得到 hello.i 檔案後在 main 函式之前便會有 printf 函式的外部宣告,接下來的編譯過程便不會出現警告資訊。當經過彙編階段得到 hello.o 可重定位目標檔案後,編譯器會自動鏈結 printf.o 可重定位檔案,最終生成可執行目標檔案。

當我們修改 main 函式後重新編譯時,編譯器會把 hello.c 程式檔案重新預處理、編譯、彙編得到 hello.o 可重定位目標檔案,然後與 printf.o 鏈結生成可執行目標檔案。編譯器當然不會重新預處理、編譯、彙編 printf.c 檔案。

我們為什麼需要睡眠

隨著時光的消逝,你是否發覺眼角的皺紋逐漸加深?變得越來越粗糙黯淡?記憶力也開始衰退?這個時候很多人都會感慨 時光易逝,容顏易老 並且開始習慣接受自己已慢慢變老,提前加入老人的行列。其實,這一切也許只是因為你長時間睡眠不足造成的。如果能夠早些了解這些常識,並引起足夠重視,你的青春也許還能保留十年。睡眠...

我們為什麼需要睡眠

隨著時光的消逝,你是否發覺眼角的皺紋逐漸加深?變得越來越粗糙黯淡?記憶力也開始衰退?這個時候很多人都會感慨 時光易逝,容顏易老 並且開始習慣接受自己已慢慢變老,提前加入老人的行列。其實,這一切也許只是因為你長時間睡眠不足造成的。如果能夠早些了解這些常識,並引起足夠重視,你的青春也許還能保留十年。睡眠...

我們為什麼需要Map Reduce?

在討論我們是否真的需要map reduce這一分布式計算技術之前,我們先面對乙個問題,這可以為我們討論這個問題提供乙個直觀的背景。我們先從最直接和直觀的方式出發,來嘗試解決這個問題 先偽一下這個問題 select count distinct surname from big name file 我...