C語言頭檔案到底是什麼?
在C語言學習的時候總是會引入這樣的語句#include ,書上解釋說把stdio.h這個檔案的全部内容直接插入到這個位置,然後再經過C語言的編譯器編譯運作。這麼看來隐含的意思好像是.h頭檔案好想并不直接參與編譯。
圍繞這個話題引出了下面這幾個問題。
一,.h頭檔案會參與編譯嗎?
不妨來做個實驗
這個是head.h檔案的内容
#include
int main() {
printf("Hello World!");
return 0;
}
這個是ori.c檔案的内容
#include "head.h"
編譯執行gcc ori.c -o ori
發現輸出的是
>> .\ori.exe
>> hello world!
.c檔案中并沒有引入任何其他的檔案,除了我們自己定義的head.h頭檔案,而在這個頭檔案中,我們引入了stdio.h頭檔案,并且我們在head.h裡面定義的main函數被執行了,由此證明了include xxx.h是直接原封不同的插入到引用這個頭檔案的.c檔案中的。
但是會參與編譯嗎?
為此設計下面這個實驗:
開一個項目,在.h頭檔案裡面定義main函數,
不引用這個頭檔案直接編譯整個項目,然後執行,
如果沒有輸出main函數内部的内容,那麼表明如果不加引用,.h檔案不參與編譯。
否則就參與編譯。
這個是head.h檔案内容
#include
int main() {
printf("Hello World! I am head.h");
return 0;
}
這個是ori.c檔案的内容
// nothing
編譯執行gcc ori.c -o ori
發現輸出的是
C:/Program Files/MinGW/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/../lib/libmingw32.a(lib64
_libmingw32_a-crt0_c.o):crt0_c.c:(.text.startup+0x2e): undefined reference to `WinMain'
collect2.exe: error: ld returned 1 exit status
報錯了,undefined reference to WinMain未定義WinMain
這時候我們在ori.c内部定義這樣的一個函數
#include
int WinMain() {
printf("Hello world! I am WinMain!");
return 0;
}
再次編譯運作gcc ori.c -o ori
運作.\ori.exe
>> .\ori.exe
>> Hello world! I am WinMain!
發現正常輸出了WinMain函數内部的内容,這也告訴我們WinMain函數同樣可以作為程式的入口。
經過實驗發現,的的确确.h絕對不是直接參與編譯的,而是通過.c檔案中#include "xxx.h"語句手動插入的。
這其實間接解答了在.h頭檔案中定義的靜态局部變量無法局部化的原因。
二, .h頭檔案中的靜态全局變量為什麼可以被通路?
我們在學習C語言初期就直到,如果對一個全局變量使用static語句修飾的話,就可以把這個變量限制在本檔案的通路域内,而無法被其他檔案通路,但是這一點對于.h檔案中無效,該通路還是可以通路?下面看一下實驗
建立三個檔案ori1.c, ori2.c, head.h
在ori1.c中寫下以下内容
#include
extern int A;
extern int B;
int main() {
printf("A = %d\n", A);
printf("B = %d", B);
return 0;
}
在ori2.c中寫下以下内容
#include
int A = 12;
static B = 13;
此時在head.h中不寫入任何内容
編譯運作
正如和我們學過的一樣,出現了未定義的錯誤
undefined reference to `B'
collect2.exe: error: ld returned 1 exit status
靜态全局變量不可以被其他變量修改
這時候我們給ori2.h添加兩個函數
void getB() {
printf("B = %d\n", B);
}
void changeB(int num) {
B = num;
}
然後在ori1.c中去聲明應用一下這兩個函數
#include
extern int A;
extern void getB();
extern void changeB(int B);
int main() {
printf("A = %d\n", A);
getB();
changeB(1600);
getB();
return 0;
}
編譯運作
>> A = 12
B = 13
B = 1600
雖然無法直接通路,但是可以定義ori2.c檔案裡面的函數去對該變量通路,這或許就是私有變量的雛形吧!
下面再來看看有關于.h中的全局變量,
由于我們已經知道了是完全插入的形式,那麼我們可以了解到這個定義的靜态全局變量是定義在引用它的檔案裡面
根據前文中定義在自己檔案内部的靜态全局變量隻能被自己通路,那麼結果自然顯而易見了,
.h檔案中的靜态全局變量作用域是引用這個頭檔案的.c檔案
自然而然的,我們應當明白,如果一個.h檔案中定義過多的全局變量,那麼這個全局變量被其他的變量引用的時候就會出現通路無指向的錯誤!重複定義
建議盡可能少在.h頭檔案中定義全局變量,或者靜态全局變量
三,條件編譯
C語言編譯是把整個項目編譯,而很多的.c檔案都需要引用同一個函數,而正對于不同的系統又出現了不同的編譯模式,如果說要把所有的.c檔案頭部都寫入那些描述條件編譯的代碼,那麼所有的代碼寫起來會複雜無比
是以把條件編譯代碼放在.h檔案裡面,直接配置好才是一個好的選擇。
條件編譯的例子
#include
#include
int main() {
#if _WIN64
system("color 0c");
printf("Hello World!");
#elif __linux__
printf("\033[22;31mHello World!\n\\033[22;30m\\");
#else
printf("Hello World!");
#endif
return 0;
}
#include
int main() {
#if _WIN64
printf("This is Windows!\n");
#else
printf("Unknown platform!\n");
#endif
#if __linux__
printf("This is Linux!\n");
#endif
return 0;
}
使用#ifdef判斷宏是否被編譯過,與之對應的還有一個#ifndef表示如果沒有被定義的話、
#include
#include
int main(){
#ifdef _DEBUG
printf("正在使用 Debug 模式編譯程式...\n");
#else
printf("正在使用 Release 模式編譯程式...\n");
#endif
system("pause");
return 0;
}
#if後面接的是整形常量表達式,而#ifdef後面隻能接宏名
又由于自己可以定義宏,自然而然的自己就可以配置出自己的項目的條件編譯。
四,一點建議
用的多的函數放在.h頭檔案中定義聲明。
盡量不要在.h頭檔案中設定全局變量,或者靜态全局變量。
在.h頭檔案中使用條件編譯控制項目的編譯,簡化代碼書寫成本。