C++ 笔记(32)— 预处理、文件包含include、宏替换define、条件包含ifndef、define
C/C++預處理器在源代碼編譯之前對其進行一些文本性質的操作。 它的主要任務包括刪除注釋 、 插入 #include 指令包含的文件的內容 、 定義和替換由 #defme 指令定義的符號以及確定代碼的部分內容是否應該根據一些條件編譯指令進行編譯 。
從概念上講,預處理器是編譯過程中單獨執行的第一個步驟。兩個最常用的預處理器指令是:
#include指令( 用于在編譯期間把指定文件的內容包含進當前文件中);#define指令( 用任意字符序列替代一個標記);
1. 預定義符號
示例代碼
int main()
{cout << "__FILE__:" << __FILE__ << endl; // __FILE__:main.cppcout << "__LINE__:" << __LINE__ << endl; // __LINE__:11cout << "__DATE__:" << __DATE__ << endl; // __DATE__:Jul 1 2021cout << "__TIME__:" << __TIME__ << endl; // __TIME__:11:57:34cout << "__STDC__:" << __STDC__ << endl; // __STDC__:1return 0;
}
2. 宏替換
宏定義的形式如下:
#define 名字 要替換的文本
這是一種最簡單的宏替換——后續所有出現名字記號的地方都將被替換為替換文本。 #define指令中的名字與變量名的命名方式相同,替換文本可以是任意字符串。通常情況下,#define指令占一行,替換文本是 #define指令行尾部的所有剩余部分內容,但也可以把一個較長的宏定義分成若干行,這時需要在待續的行末尾加上一個反斜杠符 \。
?
#define指令定義的名字的作用域從其定義點開始,到被編譯的源文件的末尾處結束。宏定義中也可以使用前面出現的宏定義。替換只對記號進行,對括在引號中的字符串不起作用。例如,如果 YES是一個通過 #define指令定義過的名字,則在 printf( "YES") 或 YESMAN中將不執行替換。
?
替換文本可以是任意的,例如:
#define forever for (;;) / * 無限循環 */
該語句為無限循環定義了一個新名字 forever。
?
宏定義也可以帶參數,這樣可以對不同的宏調用使用不同的替換文本。例如,下列宏定義定義了一個宏 max:
#define max(A, B) ((A) > (B) ? (A) : (B) )
使用宏 max 看起來很像是函數調用,但宏調用直接將替換文本插入到代碼中。形式參數( 在此為A或B ) 的每次出現都將被替換成對應的實際參數。因此,語句:
x = max(p+q, r+s) ;
將被替換為下列形式:
x = ((p+q) > (r+s) ? (p+q) : (r+s) )
仔細考慮一下 max的展開式,就會發現它存在一些缺陷。其中,作為參數的表達式要重復計算兩次,如果表達式存在副作用( 比如含有自增運算符或輸入/ 輸出),則會出現不正確的情況。例如:
max(i++, j++)
它將對每個參數執行兩次自增操作。同時還必須注意,要適當使用圓括號以保證計算次序的正確性??紤]下列宏定義:
#define square(x) x*x
當用 square(a+1)調用該宏定義時就會出現問題。
?
可以通過 #undef指令取消名字的宏定義,這樣做可以保證后續的調用是函數調用,而不是宏調用:
#undef getchar
int getchar(void)
{...
}
3. 條件編譯
使用條件編譯,你可以選擇代碼的一部分是被正常編譯還是完全忽略,用于支持條件編譯的基本結構是 #if 指令和與其匹配的 #endif 指令 。
?
#if語句對其中的常量整型表達式(其中不能包含 sizeof、類型轉換運算符或 enum常量)進行求值,若該表達式的值不等于 0,則包含其后的各行,直到遇到 #endif、#elif或 #else語句為止(預處理器語句 #elif類似于 else if)。
在 #if語句中可以使用表達式 defined(名字),該表達式的值遵循下列規則:當名字已經定義時,其值為 1 ;否則,其值為 0 。
?
例如,為了保證 hdrh文件的內容只被包含一次,可以將該文件的內容包含在下列形式的條件語句中:
#if !defined ( HDR )
#define HDR
/* hdr.h 文件的內容放在這里 */
#endif
第一次包含頭文件 hdrh時,將定義名字 HDR;此后再次包含該頭文件時,會發現該名字已經定義,這樣將直接跳轉到 #endif處。類似的方式也可以用來避免多次重復包含同一文件。
?
如果多個頭文件能夠一致地使用這種方式,那么,每個頭文件都可以將它所依賴的任何頭文件包含進來,用戶不必考慮和處理頭文件之間的各種依賴關系。
?
下面的這段預處理代碼首先測試系統變量 SYSTEM,然后根據該變量的值確定包含哪個版本的頭文件:
#if SYSTEM == SYSV#define HDR "sysv.h"
#elif SYSTEM == BSD#define HDR "bsd.h"
#elif SYSTEM == MSDOS#define HDR "msdos.h"
#else#define HDR "default.h"
#endif# include HDR
C/C++ 語言專門定義了兩個預處理語句 #ifdef 和 #ifndef 定義。上面有關 #if的第一個例子可以改寫為下列形式:
#ifndef HDR
#define HDR
/* hdr.h 文件的內容放在這里 */
#endif
4. 文件包含
4.1 庫文件和本地文件包含
文件包含指令( 即 #include指令)使得處理大量的 #define指令以及聲明更加方便。在源文件中,任何形如:
#include “文件名”
或者
#include <文件名>
的行都將被替換為由文件名指定的文件的內容。
- 如果文件名用引號引起來,則在源文件所在位置査找該文件;
- 如果在該位置沒有找到文件,或者如果文件名是用尖括號
<>括起來的,則將根據相應的規則查找該文件,這個規則同具體的實現有關;
被包含的文件本身也可包含 #include指令。
?
源文件的開始處通常都會有多個 #include指令,它們用以包含常見的 #define語句和 extern聲明,或從頭文件中訪問庫函數的函數原型聲明,比如 <stdio.h>。
?
在大的程序中, #include指令是將所有聲明捆綁在一起的較好的方法。它保證所有的源文件都具有相同的定義與變量聲明,這樣可以避免出現一些不必要的錯誤。很自然,如果某個包含文件的內容發生了變化,那么所有依賴于該包含文件的源文件都必須重新編譯。
?
4.2 嵌套文件包含
嵌套 #include 文件的缺點
- 它使得我們很難判斷源文件之間的真正依賴關系 ;
- 一個頭文件可能會被多次包含;
為了說明這 種錯誤,看下面代碼:
#include "x.h"
#include "x.h"
顯然 , 這里文件 x.h 被包含了兩次 。 沒有人會故意編寫這樣的代碼 。 但下面的代碼
#include "a.h"
#include "b.h"
看上去沒什么問題 。如果 a.h 和 b.h 都包含一個嵌套的 #include "x.h",那么 x.h 在此處也同樣出現了兩次,只不過它的形式不是那么明顯而已 。
多重包含在絕大多數情況下出現于大型程序中,它往往需要使用很多頭文件,因此要發現這種情況并不容易。要解決這個問題 , 我們可以使用條件編譯 。
#ifndef _HEADERNAME_H
#define _HEADERNAME_H
/* hdr.h 文件的內容放在這里 */
#endif
總結
以上是生活随笔為你收集整理的C++ 笔记(32)— 预处理、文件包含include、宏替换define、条件包含ifndef、define的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022-2028年中国塑料绳的制造行业
- 下一篇: 2022-2028年中国塑料管的制造行业