一、什么是預處理指令
源代碼指定了程序的定義,預處理指令(preprocessor directive)指示編譯器如何處理源代碼。例如,在某些情況下,我們希望編譯器能夠忽略一部分代碼,而在其他情況下,我們希望代碼被編譯,這時我們就可以使用預處理指令了。
C++開發人員應知道,在C 和C++中預處理器指令非常重要,但是,在C#中,並沒有那么多的預處理器指令,它們的使用也不太頻繁。C#提供了其他機制來實現許多C++指令的功能,如定制特性。還要注意,C#並沒有一個像C++那樣的獨立預處理器,所謂的預處理器指令實際上是由編譯器處理的。盡管如此,C#仍保留了一些預處理器指令名稱,因為這些命令會讓人覺得就是預處理器。
二、基本規則
知道了什么是預處理指令,那么如何使用它呢?下面是它的一些重要語法規則
♥ 預處理指令必須和C#代碼在不同的行
♥ 與C#語句不同,預處理指令不需要以分號結尾
♥ 包含預處理指令的每一行必須與 ‘’#‘’ 字符開始(在#字符前可以有空格,在#字符和指令之間也可以有空格)
♥ 允許行尾注釋
♥ 在預處理指令所在的行不允許有分隔符注釋
講了這么多,舉個栗子
#define Premium //結尾沒有分號,可以有行尾注釋,不允許分隔符注釋,即不能有如下類型的注釋/*...*/ #define Budget //前面可以有空格 # define Medium // 中間可以有空格
三、內容詳解
下表列出了預處理指令及其含義概要
指令 | 含義概要 |
#define identifier | 定義編譯符 |
#undef identifier | 取消編譯符 |
#if expression | 如果表達式為true,編譯下面的片段 |
#elif expression | 如果表達式為true,編譯下面的片段 |
#else | 如果之前的#if或#elif表達式是false,編譯下面的片段 |
#endif | 標記為一個#if結構的結束 |
#region name | 標記一段代碼的開始,沒有編譯效果 |
#endregion name | 標記一段代碼的結束,沒有編譯效果 |
#warning message | 顯式編譯時的警告消息 |
#error message | 顯式編譯時的錯誤消息 |
#line indicator | 修改在編譯器消息中顯式的行數 |
#pragma text | 指定有關程序上下文的信息 |
詳細解釋上面指令的用法
3.1 #define和#undef指令
編譯符號是只有兩種可能狀態的標識符,要么被定義,要么未被定義,它可以是除了true或false以外的任何標識符,包括C#關鍵字
#define 的用法如下所示: #define DEBUG
它告訴編譯器存在給定名稱的符號,在本例中是DEBUG。這有點類似於聲明一個變量,但這個變量並沒有真正的值,不表示字符串,只是存在而已。
這個符號不是實際代碼的一部分,而只在編譯器編譯代碼時存在。在C#代碼中它沒有任何意義。
#undef 正好相反—— 它刪除符號的定義: #undef DEBUG
如果符號不存在,#undef 就沒有任何作用。同樣,如果符號已經存在,則#define 也不起作用,允許重復定義已存在的編譯符號。必須把#define 和#undef 命令放在C#源文件的開頭位置,在聲明要編譯的任何對象的代碼之前。
#define 本身並沒有什么用,但與其他預處理器指令(特別是#if)結合使用時,它的功能就非常強大了。
這里應注意一般C#語法的一些變化。預處理器指令不用分號結束,一般一行上只有一條命令。這是因為對於預處理器指令,C#不再要求命令使用分號進行分隔。如果它遇到一條預處理器令, 就會假定下一條命令在下一行上。
3.2 條件編譯(#if、#elif、#else 和#endif)
條件編譯允許我們根據某個編譯符號是否被定義標注一段代碼編譯或跳過,條件是一個返回true或false的簡單表達式
參數類型 | 意義 | 運算結果 |
編譯符號 | 使用#define指令(未)定義的標識符 | true:標識符已使用#define定義 false:其他 |
表達式 | 使用符號和操作符!、==、!=、&&、|| 構建的 | true:如果表達式運算結果為true false:其他 |
如上圖所示,在#if和#elif指令中使用的條件可以由單個編譯符號、符號表達式或操作符組成。子條件可以使用圓括號分組。 文本true或false也可以在條件表達式中使用
#define Premium #define Budget using System; namespace 預處理指令 { class Program { static void Main(string[] args) { #if Premium&&Budget Console.WriteLine("defined!"); //輸出defined! #endif #if Hello Console.WriteLine("defined!"); //Hello未被定義,不輸出 #else Console.WriteLine("undefined!"); //輸出undefined! #endif Console.ReadKey(); } } }
當編譯器遇到#if 語句后,將先檢查相關的符號是否存在,如果符號存在,就編譯#if 子句中的代碼。否則,編譯器會忽略所有的代碼,直到遇到匹配的#endif 指令為止。
#elif (=else if),其用法及含義十分直觀,就不舉例子了
值得注意的是,#if和#endif指令在條件編譯結構中必須配對使用,只要有#if指令,就必須有#endif配對
3.3 診斷指令(#warning和#error)
診斷指令產生用戶自定義的編譯時的警告或錯誤消息,它的用法為
#warning Message
Message是字符串,它與普通的C#字符串不同,不需要被引號包圍
#define Premium #define Budget using System; namespace 預處理指令 { class Program { static void Main(string[] args) { #if Premium && Budget #error Can't build this code #endif #warning Remember to come back to check this code Console.ReadKey(); } } }
可以看到,編譯器中出現了對應的錯誤和警告提醒
3.4 行號指令(#line)
行號指令可以做許多事,例如
♥ 改變由編譯器警告和錯誤消息報告的出現行數
♥ 改變被編譯源文件的文件名
♥ 對交互式調試器隱藏一些行
class MainClass { static void Main() { #line 200 "Special" //設置下一行的行號值為200,並將編譯源文件的名字改成“Special” int i; int j; #line default //重新保存實際的行號和文件名 char c; float f; #line hidden // 在斷點調試器中隱藏代碼 string s; double d;
#line //停止在調試器中隱藏代碼 } }
編譯產生以下輸出
Special(200,13): warning CS0168: The variable 'i' is declared but never used
Special(201,13): warning CS0168: The variable 'j' is declared but never used
MainClass.cs(9,14): warning CS0168: The variable 'c' is declared but never used
MainClass.cs(10,15): warning CS0168: The variable 'f' is declared but never used
MainClass.cs(12,16): warning CS0168: The variable 's' is declared but never used
MainClass.cs(13,16): warning CS0168: The variable 'd' is declared but never used
仔細觀察輸出結果你將可以發現其中的不同,使用時要注意的是:
要改變外觀文件名,可以在雙引號內使用文件名作為參數,雙引號是必須的
要返回真是的文件名和行號,可以使用default參數
要對交互調試器的斷點調試功能隱藏代碼段,可以使用hidden參數。要停止隱藏,可以使用不帶任何參數的指令
#line hidden
指令能對調試程序隱藏連續行,當開發者逐行執行代碼時,介於 #line hidden
和下一 #line
指令(假設它不是其他 #line hidden
指令)間的任何行都將被跳過。它不影響錯誤報告中的文件名或行號。 也就是說,如果在隱藏塊中遇到錯誤,編譯器將報告錯誤的當前文件名和行號。
3.5 區域指令(#region和#endregion)
利用 #region
,可以指定在使用 Visual Studio Code 編輯器的大綱功能時可展開或折疊的代碼塊。 在較長的代碼文件中,能夠折疊或隱藏一個或多個區域會十分便利,這樣,可將精力集中於當前處理的文件部分。
#define Premium #define Budget using System; namespace 預處理指令 { #region program //名字為program class Program { static void Main(string[] args) { #line 89 "special" int c; #line default #if Budgets Console.WriteLine("defined!"); #else Console.WriteLine("undefined!"); #endif Console.ReadKey(); } } #endregion //結束標記 }
在使用#region的地方出現了可以折疊的標志
備注:
#region
塊必須通過 #endregion 指令終止。
#region
塊不能與 #if塊重疊。 但是,可以將 #region
塊嵌套在 #if
塊內,或將 #if
塊嵌套在 #region
塊內。
#region指令后的可選字符串文本作為其名字
3.6 #pragma warning指令
#pragma warning指令允許我們關閉或重新開啟warning指令
要關閉警告消息,可以使用disable加上逗號分隔希望關閉的警告數列表
要開啟警告消息,可以使用restore加上逗號分隔希望開啟的警告數列表
using System; #pragma warning disable 414 //關閉414行的警告 public class C { int i = 1; static void Main() { } } #pragma warning restore //所有警告消息在下面代碼中處於開啟狀態 public class D { int i = 1; public static void F() { } }
如果還有夢就追,至少不會遺憾后悔!