在C語言程式中加入一些編譯預先處理命令可以提高編程效率,回憶編譯速度。預先處理命令是在編譯來源程式前先對來源程式進行處理,例如,在程式中使用“#defineMAX 256“這條命令定義一個符號常量MAX,則在預先處理時將程式中出現的所有MAX替換為256。預先處理完成後,編譯器(如gcc)開始編譯來源程式以產生可執行代碼。需要注意的是,預先處理命令並不是C語言的一部分,因此每條編譯預先處理命令不需要以分號來結束。
一、宏定義
C語言標準允許在程式中用一個標識符來表示一個字串,稱為宏。標識符稱為宏名。在編譯預先處理時,將程式中所有的宏名用相應的字串來替換,這個過程稱為宏替換。宏分為兩種:無參數的宏和有參數的宏。
1、無參數宏
無參數宏定義的一般形式為:
#define 標識符 字串
“#”代表本行是編譯預先處理命令。define是宏定義的關鍵詞,標識符是宏名。字串是宏名所代替的內容,可以是常數、運算式等。
注意:宏定義和其他編譯預先處理命令不是以分號結尾的。
例5-1 下面是一個使用無參數宏的程式
#include <stdio.h>
#define PI 3.1415926
int main()
{
int r = 100;
double length = 2*PI*r;
printf("The circumference is %f\n",length);
return 0;
}
程式說明:(1)本程式使用PI來代表3.1415926。宏替換是在程式中用相應的字串來替換害名,編譯器預先處理程式對它不作任何檢查。如果有錯誤,只能在編譯器時才會被編譯器發現。(2)習慣上,宏名都用大寫字母。當然也可以用小寫字母。(3)使用宏代替一個字串,可以減少程式中重複書寫某些字串的工作量。可以用一個有意義的宏句來代表無規律的字串,提高程式的可讀性,同時修改起來也方便。如果要把程式中的PI值改為3.14,則只要修改#define這一行即可。如果沒有使用宏,那麼就要尋找程式並修改所有的PI值。(4)宏的作用範圍是從宏定義開始到本來源程式檔案結束為止。也可以使用#undef來提前終止作用範圍。例如:
#define MAX 256
int main()
{
...
}
#undef MAX
int f()
{
...
}
由於使用了#undef,使宏名MAX只在main函數中有效。(5)宏定義允許嵌套。例如:
#define MIN 128
#define MAX MIN*2
定義MAX宏時使用了前面已經定義的MIN。
2、有參數宏
有參數宏的宏類似於有參數的函數,其定義的一般形式為:
#define 標識符(形參表) 字串
如果有多個形參,像函數參數一樣以逗號隔開。在程式中使用有參數宏的形式是:
標識符(實參表)
例5-2示範了有參數宏的實現方法。
#include <stdio.h>
#define MAX(x,y) (x>y?x:y)
int main()
{
int a = 5,b = 10,max;
max = MAX(5,10);
printf("The max between(%d,%d) is %d\n",a,b,max);
return 0;
}
程式說明:經過編譯預先處理max = MAX(a,b)就替換max=(a>b?a:b)。
程式第二行的宏定義中運算式x>y?x:y兩邊的括弧不是必需的,但出於良好的編程規範應該加上。如果沒有括弧往往會導致一些意想不到的問題。比如有一個宏定義:
#defineMUL(x,y) x*y
在程式中有:
int a=5,b=10,c;
c=MUL(a+1,b+1);
那麼進行宏替換,a+1是x的實參,b+1是y的實參,替換後的結果為:
c=a+1*b+1;
顯然這是不符合要求的。應該按照如下方式進行宏定義:
#define MUL(x,y) (x)*(y)
此時宏展開後:
c = (a+1)*(b+1);
定義有參數的宏時,應該注意:
宏名與形參表的圓括弧之間不能有空格,否則會導致錯誤。例如:#defineMUL(x,y) (x)*(y),MUL與“(”之間不能有空格。
宏定義中,字串內的形式參數最好用括弧括起來,以避免錯誤。例如上面的形參都用括弧括起來。
帶參數的宏與函數的比較:
有參數宏的形式參數不是變數,不分配記憶體空間,無需說明資料類型。而函數的形參是變數,要分配記憶體空間,在函數定義時要指明參數的資料類型。
預先處理程式認為有參數宏的是字串,並用它去替換形參。如上面的例子中,用a+1去替換x,而不是先計算a+1的值再去替換x。如果是函數,則先計算a+1的值,再把這個值傳遞給x。
使用宏的次數較多時,宏替換後來源程式一般會變長。而函數調用不會使程式變長。宏替換不會佔用已耗用時間,只是編譯的時間稍微變長一點。而函數調用則會佔用已耗用時間。一般用宏來代表一些較為簡單的運算式比較合適。
二、檔案包含
檔案包含預先處理命令#include前面已經使用過了。它把指定源檔案的全部內容包括到當前來源程式檔案中,其一般形式為:
#include <檔案名稱>
或者
#include "檔案名稱”
檔案包含命令是把指定檔案的全部內容包括進來,插入到命令所在位置,取代原來的命令列。由當前源檔案和指定檔案組成一個檔案,一起進行編譯。
一個#include只能包含一個檔案,要包含多個檔案,需要使用多個#include命令。例如:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
一個大的程式往往被分成多個模組,由多個程式員分別編寫。公用的資訊,如常量定義、函式宣告,可以單獨放在一個檔案中,在其他檔案的開頭使用#include命令包含進來。這樣可以避免每個檔案頭部都去書寫那些公用量,既節省了時間又可以防止出錯的可能。
檔案包含命令中的檔案名稱既可以用角括弧也可以用雙引號括起來,它們的區別在於尋找指定檔案的位置不同。角括弧只在預設目錄裡找指定檔案,預設目錄是由使用者佈建的編程環境決定的。雙引號則先在來源程式檔案所在的目前的目錄裡尋找指定檔案,如果沒有找到再到預設目錄裡找。如果指定檔案與當前編寫中的來源程式處在同一個目錄裡,就必須使用雙引號來包含該檔案,否則編譯器時編譯器會報告找不到指定的標頭檔。
三、條件編譯
一般情況下,來源程式中所有的行都被編譯。有時希望其中一部分內容只在某個條件成立或不成立時才去編譯,也就是對一部分內容指定編譯的條件,這就是條件編譯。
條件編譯使用範式:
1、範式一
#ifndef 標識符
程式段1
#endif
其含義是:如果沒有定義標識符,就編譯器段1。這裡的程式段1既可以是語句組,也可以是命令列。使用樣本:
#ifndef _getkey_h
#define _getkey_h
#include<sys/types.h>
#endif
這段代碼的含義是:如果沒有定義符號常量_getkey_h,就定義該常量並且包含標頭檔sys/types.h。
2、範式2
#ifndef 標識符
程式段1
#else
程式段2
#endif
其含義是:如果沒有定義標識符,就編譯器段1,否則編譯器段2。
3、範式3
#ifdef 標識符
程式段1
#endif
其含義是:如果定義了標識符,就編譯器段1,否則不編譯該程式段。
使用樣本:
#ifdef DEBUG
printf("a=%d,b=%d",a,b);
#endif
在偵錯工具時,可以在來源程式頭部加入如下語句:
#define DEBUG
這樣在軟體開發階段,編譯運行程式時會輸出變數a,b的值。當程式調試完畢,在來源程式檔案頭部刪除這一行,則使用者運行時不會輸出a,b的值。這裡列印出a,b值只是供調試使用。
4、範式四
#ifdef 標識符
程式段1
#else
程式段2
#endif
其含義是:如果定義了標識符,就編譯器段1,否則編譯器段2。
5、範式五
#if 運算式
程式段1
#endif
其含義是:如果運算式成立,就編譯器段1,否則不編譯該程式段。
使用樣本:
#include <stdio.h>
#define MAX(x,y) (x>y?x:y)
...
int a = 5,b = 10,c;
...
#if c
c = MAX(a,b);
#endif
如果變數c存在,就調用宏MAX(a,b)獲得a,b的最大值,並把該值賦給變數c。
6、範式六
#if 運算式
程式段1
#else
程式段2
#endif
其含義是:如果運算式成立,就編譯器段1,否則編譯器段2。
事實上,不用條件編譯而直接用if...else語句也可以達到要求。但採用條件編譯,可以減少被編譯的語句,從而減少可執行程式的長度,縮短程式已耗用時間。當條件編譯的程式段比較多時,可執行程式的長度可以大大減少。