C#
的預處理機制
預處理過程掃描源代碼,對其進行初步的轉換,產生新的源代碼提供給編譯器。可見預處理過程先於編譯
器對源代碼進行處理。
在
C
語言中,並沒有任何內在的機制來完成如下一些功能:在編譯時包含其他源文件、定義宏、根據條件
決定編譯時是否包含某些代碼。要完成這些工作,就需要使用預處理程序。盡管在目前絕大多數編譯器都
包含了預處理程序,但通常認為它們是獨立於編譯器的。預處理過程讀入源代碼,檢查包含預處理指令的
語句和宏定義,並對源代碼進行響應的轉換。預處理過程還會刪除程序中的注釋和多余的空白字符。
預處理指令是以
#
號開頭的代碼行。
#
號必須是該行除了任何空白字符外的第一個字符。
#
后是指令關鍵字,
在關鍵字和
#
號之間允許存在任意個數的空白字符。
整行語句構成了一條預處理指令,
該指令將在編譯器進
行編譯之前對源代碼做某些轉換。下面是部分預處理指令:
指令
用途
#
空指令,無任何效果
#include
包含一個源代碼文件
#define
定義宏
#undef
取消已定義的宏
#if
如果給定條件為真,則編譯下面代碼
#ifdef
如果宏已經定義,則編譯下面代碼
#ifndef
如果宏沒有定義,則編譯下面代碼
#elif
如果前面的
#if
給定條件不為真,當前條件為真,則編譯下面代碼
#endif
結束一個
#if……#else
條件編譯塊
#error
停止編譯並顯示錯誤信息
一、文件包含
#include
預處理指令的作用是在指令處展開被包含的文件。包含可以是多重的,也就是說一個被包含的
文件中還可以包含其他文件。標准
C
編譯器至少支持八
重嵌套包含。
預處理過程不檢查在轉換單元中是否已經包含了某個文件並阻止對它的多次包含。這樣就可以在多次包
含同一個頭文件時,通過給定編譯時的條件來達到不
同的效果。例如:
for(i=0;i<MAX_NUM;i++)
/*……*/
在這個例子中,對於閱讀該程序的人來說,符號
MAX_NUM
就有特定的含義,它代表的值給出了數
組所能容納的最大元素數目。程序中可以多次使用這個值。作為一種約定,習慣上總是全部用大寫字母來
定義宏,這樣易於把程序紅的宏標識符和一般變量標識符區別開來。如果想要改變數組的大小,只需要更
改宏定義並重新編譯程序即可。
宏表示的值可以是一個常量表達式,其中允許包括前面已經定義的宏標識符。例如:
#define ONE 1
#define TWO 2
#define THREE (ONE+TWO)
注意上面的宏定義使用了括號。盡管它們並不是必須的。但出於謹慎考慮,還是應該加上括號的。例
如:
six=THREE*TWO;
預處理過程把上面的一行代碼轉換成:
six=(ONE+TWO)*TWO;
如果沒有那個括號,就轉換成
six=ONE+TWO*TWO;
了。
宏還可以代表一個字符串常量,例如:
#define VERSION "Version 1.0 Copyright(c) 2003"
2.
帶參數的
#define
指令
帶參數的宏和函數調用看起來有些相似。看一個例子:
#define Cube(x) (x)*(x)*(x)
可以時任何數字表達式甚至函數調用來代替參數
x
。這里再次提醒大家注意括號的使用。宏展開后完
全包含在一對括號中,而且參數也包含在括號中,這樣就保證了宏和參數的完整性。看一個用法:
intnum=8+2;
volume=Cube(num);
展開后為
(8+2)*(8+2)*(8+2);
如果沒有那些括號就變為
8+2*8+2*8+2
了。
下面的用法是不安全的:
volume=Cube(num++);
如果
Cube
是一個函數,上面的寫法是可以理解的。但是,因為
Cube
是一個宏,所以會產生副作用。
這里的擦書不是簡單的表達式,它們將產生意想不到的結果。它們展開后是這樣的:
volume=(num++)*(num++)*(num++);
很顯然,結果是
10*11*12,
而不是
10*10*10;
那么怎樣安全的使用
Cube
宏呢?必須把可能產生副作用的操作移到宏調用的外面進行:
intnum=8+2;
volume=Cube(num);
num++;
3.#
運算符
出現在宏定義中的
#
運算符把跟在其后的參數轉換成一個字符串。有時把這種用法的
#
稱為字符串化運
算符。例如:
#define PASTE(n) "adhfkj"#n
main()
{
printf("%s"n",PASTE(15));
}
宏定義中的
#
運算符告訴預處理程序,把源代碼中任何傳遞給該宏的參數轉換成一個字符串。所以輸
出應該是
adhfkj15
。
4.##
運算符
##
運算符用於把參數連接到一起。
預處理程序把出現在
##
兩側的參數合並成一個符號。
看下面的例子:
#define NUM(a,b,c) a##b##c
#define STR(a,b,c) a##b##c
main()
{
printf("%d"n",NUM(1,2,3));
printf("%s"n",STR("aa","bb","cc"));
}
最后程序的輸出為
:
123
aabbcc
千萬別擔心,除非需要或者宏的用法恰好和手頭的工作相關,否則很少有程序員會知道
##
運算符。絕
大多數程序員從來沒用過它。
三、條件編譯指令
條件編譯指令將決定那些代碼被編譯,而哪些是不被編譯的。可以根據表達式的值或者某個特定的宏是
否被定義來確定編譯條件。
1.#if
指令
#if
指令檢測跟在制造另關鍵字后的常量表達式。
如果表達式為真,
則編譯后面的代碼,
知道出現
#else
、
#elif
或
#endif
為止;否則就不編譯。
2.#endif
指令
#endif
用於終止
#if
預處理指令。
#define DEBUG 0
main()
{
#if DEBUG
printf("Debugging"n");
#endif
printf("Running"n");
}
由於程序定義
DEBUG
宏代表
0
,所以
#if
條件為假,不編譯后面的代碼直到
#endif
,所以程序直接輸
出
Running
。
如果去掉
#define
語句,效果是一樣的。
3.#ifdef
和
#ifndef
#define DEBUG
main()
{
#ifdef DEBUG
printf("yes"n");
#endif
#ifndef DEBUG
printf("no"n");
#endif
}
#if defined
等價於
#ifdef; #if !defined
等價於
#ifndef
4.#else
指令
#else
指令用於某個
#if
指令之后,當前面的
#if
指令的條件不為真時,就編譯
#else
后面的代碼。
#endif
指令將中指上面的條件塊。
#define DEBUG
main()
{
#ifdef DEBUG
printf("Debugging"n");
#else
printf("Not debugging"n");
#endif
printf("Running"n");
}
5.#elif
指令
#elif
預處理指令綜合了
#else
和
#if
指令的作用。
#define TWO
main()
{
#ifdef ONE
printf("1"n");
#elif defined TWO
printf("2"n");
#else
printf("3"n");
#endif
}
程序很好理解,最后輸出結果是
2
。
6.
其他一些標准指令
#error
指令將使編譯器顯示一條錯誤信息,然后停止編譯。
#line
指令可以改變編譯器用來指出警告和錯誤信息的文件號和行號。
#pragma
指令沒有正式的定義。編譯器可以自定義其用途。典型的用法是禁止或允許某些煩人的警告
信息。