C語言頭文件到底是什么?
- 在C語言學習的時候總是會引入這樣的語句
#include <stdio.h>
,書上解釋說把stdio.h
這個文件的全部內容直接插入到這個位置,然后再經過C語言的編譯器編譯運行。這么看來隱含的意思好像是.h
頭文件好想並不直接參與編譯。- 圍繞這個話題引出了下面這幾個問題。
一,.h
頭文件會參與編譯嗎?
- 不妨來做個實驗
這個是head.h
文件的內容
#include <stdio.h>
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 <stdio.h>
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 <stdio.h>
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 <stdio.h>
extern int A;
extern int B;
int main() {
printf("A = %d\n", A);
printf("B = %d", B);
return 0;
}
在ori2.c
中寫下以下內容
#include <stdio.h>
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 <stdio.h>
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 <stdio.h>
#include <stdlib.h>
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 <stdio.h>
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 <stdio.h>
#include <stdlib.h>
int main(){
#ifdef _DEBUG
printf("正在使用 Debug 模式編譯程序...\n");
#else
printf("正在使用 Release 模式編譯程序...\n");
#endif
system("pause");
return 0;
}
#if
后面接的是整形常量表達式,而#ifdef
后面只能接宏名
又由於自己可以定義宏,自然而然的自己就可以配置出自己的項目的條件編譯。
四,一點建議
- 用的多的函數放在
.h
頭文件中定義聲明。- 盡量不要在
.h
頭文件中設置全局變量,或者靜態全局變量。- 在
.h
頭文件中使用條件編譯控制項目的編譯,簡化代碼書寫成本。