預處理器指令

預處理器指令是程式程式碼中以井號 (#) 開頭的程式碼行。這些程式碼行不是程式語句,而是給預處理器的指令。預處理器在程式碼的實際編譯開始之前檢查程式碼,並在常規語句實際生成任何程式碼之前解析所有這些指令。

這些預處理器指令只佔用單行程式碼。一旦遇到換行符,預處理器指令就結束了。預處理器指令的末尾不需要分號 (;)。讓預處理器指令跨越多行的唯一方法是在行尾的換行符前加上一個反斜槓 (\)。

宏定義 (#define, #undef)

要定義預處理器宏,我們可以使用 #define。其語法是:

#define 識別符號 替換內容

當預處理器遇到此指令時,它會將程式碼其餘部分中所有出現的 identifier(識別符號)替換為 replacement(替換內容)。這個 replacement 可以是一個表示式、一個語句、一個程式碼塊或任何東西。預處理器本身不理解 C++,它只是簡單地將所有出現的 identifier 替換為 replacement

1
2
3
#define TABLE_SIZE 100
int table1[TABLE_SIZE];
int table2[TABLE_SIZE];

在預處理器替換了 TABLE_SIZE 之後,程式碼變得等同於:

1
2
int table1[100];
int table2[100];

#define 也可以與引數一起使用來定義函式宏:

1
#define getmax(a,b) a>b?a:b 

這將會把任何後面跟著兩個引數的 getmax 出現的地方,替換為替換表示式,同時也會將每個引數替換為其識別符號,正如你期望一個函式那樣工作。

// function macro
#include <iostream>
using namespace std;

#define getmax(a,b) ((a)>(b)?(a):(b))

int main()
{
  int x=5, y;
  y= getmax(x,2);
  cout << y << endl;
  cout << getmax(7,x) << endl;
  return 0;
}
5
7

已定義的宏不受程式碼塊結構的影響。一個宏會一直有效,直到用 #undef 預處理器指令取消定義它為止。

1
2
3
4
5
#define TABLE_SIZE 100
int table1[TABLE_SIZE];
#undef TABLE_SIZE
#define TABLE_SIZE 200
int table2[TABLE_SIZE];

這將生成與以下程式碼相同的程式碼:

1
2
int table1[100];
int table2[200];

函式宏定義在替換序列中接受兩個特殊運算子(###)。
運算子 # 後面跟著一個引數名,它會被替換為一個包含傳入引數的字串字面量(就像用雙引號括起來一樣)。
1
2
#define str(x) #x
cout << str(test);

這將被翻譯成:

1
cout << "test";

運算子 ## 連線兩個引數,它們之間不留任何空格。

1
2
#define glue(a,b) a ## b
glue(c,out) << "test";

這同樣會被翻譯成:

1
cout << "test";

因為預處理器替換髮生在任何 C++ 語法檢查之前,所以宏定義可能是一個棘手的特性。但是,要小心:嚴重依賴複雜宏的程式碼會變得可讀性較差,因為其預期的語法在很多情況下與程式設計師在 C++ 中所期望的正常表示式不同。

條件包含 (#ifdef, #ifndef, #if, #endif, #else 和 #elif)


這些指令允許在滿足特定條件時,包含或捨棄程式的一部分程式碼。

#ifdef 允許一段程式程式碼只有在作為引數指定的宏已經被定義時才被編譯,無論其值是什麼。例如:

1
2
3
#ifdef TABLE_SIZE
int table[TABLE_SIZE];
#endif  

在這種情況下,程式碼行 int table[TABLE_SIZE]; 只有在 TABLE_SIZE 之前已經用 #define 定義過才會被編譯,而不管它的值是多少。如果它沒有被定義,那行程式碼將不會被包含在程式編譯中。

#ifndef 的作用恰恰相反:在 #ifndef#endif 指令之間的程式碼,只有在指定的識別符號之前沒有被定義時才會被編譯。例如:

1
2
3
4
#ifndef TABLE_SIZE
#define TABLE_SIZE 100
#endif
int table[TABLE_SIZE];

在這種情況下,如果程式執行到這段程式碼時,宏 TABLE_SIZE 還沒有被定義,那麼它將被定義為 100。如果它已經存在,它將保持其先前的值,因為 #define 指令將不會被執行。

#if#else#elif(即 "else if")指令用於指定要滿足的某個條件,以使其包圍的程式碼部分被編譯。#if#elif 後面的條件只能評估常量表達式,包括宏表示式。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#if TABLE_SIZE>200
#undef TABLE_SIZE
#define TABLE_SIZE 200
 
#elif TABLE_SIZE<50
#undef TABLE_SIZE
#define TABLE_SIZE 50
 
#else
#undef TABLE_SIZE
#define TABLE_SIZE 100
#endif
 
int table[TABLE_SIZE];

請注意,整個由 #if#elif#else 連結起來的指令結構以 #endif 結尾。

#ifdef#ifndef 的行為也可以透過在任何 #if#elif 指令中使用特殊運算子 defined!defined 來實現。

1
2
3
4
5
6
7
#if defined ARRAY_SIZE
#define TABLE_SIZE ARRAY_SIZE
#elif !defined BUFFER_SIZE
#define TABLE_SIZE 128
#else
#define TABLE_SIZE BUFFER_SIZE
#endif 

行控制 (#line)

當我們編譯一個程式並且在編譯過程中發生錯誤時,編譯器會顯示一條錯誤訊息,其中包含錯誤發生的檔名和行號,以便更容易地找到產生錯誤的程式碼。

#line 指令允許我們控制這兩樣東西:程式碼檔案中的行號以及發生錯誤時我們希望出現的檔名。其格式為:

#line 行號 "檔名"

其中 number(行號)是將被賦給下一行程式碼的新行號。後續程式碼行的行號將從此開始逐一增加。

"filename"(檔名)是一個可選引數,允許重新定義將要顯示的檔名。例如:

1
2
#line 20 "assigning variable"
int a?;

這段程式碼將產生一個錯誤,該錯誤將顯示為在檔案 "assigning variable" 的第 20 行。

錯誤指令 (#error)

此指令在被發現時會中止編譯過程,並生成一個編譯錯誤,該錯誤可以作為其引數指定。

1
2
3
#ifndef __cplusplus
#error A C++ compiler is required!
#endif 

這個例子會在宏名 __cplusplus 未定義時中止編譯過程(這個宏名在所有 C++ 編譯器中都是預設定義的)。

原始檔包含 (#include)

本教程的其他部分已經頻繁使用過此指令。當預處理器發現一個 #include 指令時,它會將其替換為指定的標頭檔案或檔案的全部內容。有兩種使用 #include 的方式:

1
2
#include <header>
#include "file" 

在第一種情況下,標頭檔案被指定在尖括號 <> 之間。這用於包含由實現提供的標頭檔案,例如構成標準庫的標頭檔案(iostream, string, ...)。這些標頭檔案是實際的檔案還是以其他形式存在是由實現定義的,但無論如何,它們都應該用這個指令正確地包含。

第二種 #include 使用的語法是引號,它包含一個檔案。系統會以由實現定義的方式搜尋該檔案,這通常包括當前路徑。如果找不到檔案,編譯器會將該指令解釋為標頭檔案包含,就像引號 ("") 被替換為尖括號 (<>) 一樣。

Pragma 指令 (#pragma)

該指令用於向編譯器指定各種選項。這些選項特定於您使用的平臺和編譯器。有關您可以使用 #pragma 定義的可能引數的更多資訊,請查閱您的編譯器手冊或參考資料。

如果編譯器不支援 #pragma 的特定引數,它將被忽略——不會產生語法錯誤。

預定義的宏名

以下宏名總是被定義的(它們都以兩個下劃線字元 _ 開始和結束):

__LINE__代表正在編譯的原始碼檔案中當前行的整數值。
__FILE__一個字串字面量,包含正在編譯的原始檔的假定名稱。
__DATE__一個形式為 "Mmm dd yyyy" 的字串字面量,包含編譯過程開始的日期。
__TIME__一個形式為 "hh:mm:ss" 的字串字面量,包含編譯過程開始的時間。
__cplusplus一個整數值。所有 C++ 編譯器都將此常量定義為某個值。其值取決於編譯器支援的標準版本:
  • 199711L: ISO C++ 1998/2003
  • 201103L: ISO C++ 2011
不符合標準的編譯器會將此常量定義為最多五位長的某個值。請注意,許多編譯器並未完全符合標準,因此其定義的此常量值可能不是上述任何一個值。
__STDC_HOSTED__如果實現是託管實現(所有標準標頭檔案都可用),則為 1
否則為 0

以下宏是可選定義的,通常取決於某個功能是否可用:

__STDC__在 C 語言中:如果定義為 1,則該實現符合 C 標準。
在 C++ 中:由實現定義。
__STDC_VERSION__在 C 語言中:
  • 199401L: ISO C 1990, 修正案 1
  • 199901L: ISO C 1999
  • 201112L: ISO C 2011
在 C++ 中:由實現定義。
__STDC_MB_MIGHT_NEQ_WC__如果多位元組編碼可能在字元字面量中賦予字元不同的值,則為 1
__STDC_ISO_10646__一個形式為 yyyymmL 的值,指定了 wchar_t 字元編碼所遵循的 Unicode 標準的日期。
__STDCPP_STRICT_POINTER_SAFETY__如果實現具有嚴格指標安全性,則為 1 (參見 get_pointer_safety)。
__STDCPP_THREADS__如果程式可以擁有多個執行緒,則為 1

特定的實現可能會定義額外的常量。

例如:

// standard macro names
#include <iostream>
using namespace std;

int main()
{
  cout << "This is the line number " << __LINE__;
  cout << " of file " << __FILE__ << ".\n";
  cout << "Its compilation began " << __DATE__;
  cout << " at " << __TIME__ << ".\n";
  cout << "The compiler gives a __cplusplus value of " << __cplusplus;
  return 0;
}
This is the line number 7 of file /home/jay/stdmacronames.cpp.
Its compilation began Nov  1 2005 at 10:12:29.
The compiler gives a __cplusplus value of 1
Index
目錄