C語言-標頭檔與前置處理器
簡介
這一個章節要介紹如何撰寫標頭檔,以及如何使用前置處理器
前置處理是在程式編譯之前所做的事,包括引入標頭檔、定義符號常數以及巨集
所有前置處理都是由井字號’#’開始
標頭檔與實作檔
首先我們要先了解程式從撰寫到執行的過程,請看下圖
程式原始碼在經過編譯器(Compiler)編譯之後會成為目的檔(Object file)
然後多個目的檔透過連結器(Linker)連結之後會形成可執行檔(Executable file)
之後可執行檔經過載入器(Loader)載入到記憶體中執行
當程式越來越龐大的時候,我們會為了保持彈性將單一功能切割為標頭檔跟實作檔
實作檔編譯成目的檔,要使用時再連結起來就行
這樣以後只要有錯誤只要處理小部分的程式
而標頭檔則提供介面,讓使用這段功能的人不用了解實作也能使用程式
以後實作檔改變了,只要重新編譯實作檔然後再連結一次就可以更改程式了
提高程式撰寫的彈性,而連結又分為靜態跟動態連結
這時候就要進入工商時間(推坑時間)啦
可以找程式設計師的自我修養:連結、載入、程式庫這本書來看
可以更了解程式如何連結與執行
詳細可以參考這個連結
以下會寫一個標頭檔,一個實作檔,一個執行檔當作範例
標頭檔 calc.h
1 | //宣告函數原型 |
實作檔 calc.c
1 |
|
執行檔 main.c
1 |
|
執行結果
a = 5
b = 3
add(a,b) = 8
sub(a,b) = 2
這邊要注意的是,如果是用箭頭<>括起來的標頭檔
會從系統預設的路徑去找標頭檔
而用雙引號””括起來的標頭檔,系統會到被編譯的檔案所在的目錄去找
#define
#define的語法如下
1 |
#define語法會在編譯過程中,將所有識別字都替換為代換文字,例如
1 |
之後整個程式只要看到’PI’就會自動被替換成3.14159,例如
1 | double area(double radius){ |
注意這不是宣告常數,所以不要在代換文字前面加上等號’=’
以下是範例程式
1 |
|
執行結果
請輸入半徑:4
圓面積:50.265440
巨集是由#define所定義的運算,跟符號常數一樣都是在編譯的時候代換文字
不同的是巨集可以有引數,以下是計算圓面積的巨集
1 |
之後只要寫到AREA()就會自動展開為以下算式
1 | double area = AREA(4) |
這邊要小心的是括號問題
如果沒有寫好括號,寫成((PI * x * x)),會展開成以下結果
1 | double area = AREA(4 + 2) |
所以將引數用括號括起來是非常重要的
如果要代換的文字太長,可以使用反斜線\表示下一行還有代換文字
以下是兩整數交換寫成巨集的範例
1 |
|
這邊要強調的是,巨集並不是函數,只是在編譯過程中,將文字取代成代換文字
不使用巨集跟常數符號的時候,可以使用#undef來解除定義
條件編譯
條件編譯可以讓設計者控制哪段程式要怎麼編譯
例如不同作業系統就編譯不同的程式碼,這樣就不用分開成兩個檔案去編譯了
以下是常看得的NULL的條件編譯
1 |
以上判斷NULL是否曾經定義為符號常數,若沒有就定義NULL為0
也可以簡寫成
1 |
只要是條件編譯都需要用#endif做結尾
另外還有指令#if defined(名稱),可以簡寫成#ifdef
而如果有多個條件可以使用#elif跟#else,就像if-else一樣
條件編譯常用的用途可以在標頭檔前面看到
這邊再改寫上面標頭檔的實作當作範例
1 |
|
實作檔 calc.c
1 |
|
執行檔 main.c
1 |
|
為什麼這樣寫是為了避免重複引入,例如A標頭檔有引入stdio
B標頭檔也有引入stdio,如果沒有使用條件編譯
這樣會在編譯過程中產生重複宣告的錯誤
所以為了避免重複宣告,利用條件編譯,只要有定義過就不用再定義
以避免錯誤