【C++ 筆記】前置處理器(preprocessor) - part 33
【C++ 筆記】前置處理器(preprocessor) - part 33
很感謝你點進來這篇文章。
你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧!
Introduction
preprocessor 是在 C / C++ 編譯前對 Source Code(.cpp files)進行處理的一種工具。
It does many tasks such as including header files, conditional compilation, text substitution, removing comments, etc.
From GeeksForGeeks
preprocessor 可以做到如下這些事情:
- 引入標頭檔
- 條件編譯
- 文本替換
- 移除註解
- 等等
另外也可以讓開發者去選擇說,哪些程式需要被保留(included)或是不需要被保留(excluded)的。
經 preprocessor 處理過後的程式碼,通常都被稱為是「已被展開的程式碼(expanded code)」,都由 .i 這個副檔名去做儲存的動作。
前置處理主要是在編譯器編譯前,生出一份乾淨的 .c / .cpp 檔案給它,方便日後處理。
我們所有的前置處理指令(directives)都由 # 符號作為開頭,如我們最常見的 #include 就是之一。然後這個指令不是 C++ 語法的原因,所以不需要加上分號以示結束。
#include
這個指令就是把其他檔案的內容包含到目前的檔案之中。
常見的就是我們把 iostream 這個 header file 標頭檔包含到目前檔案中。
Syntax :
1 |
用雙引號或是兩個大於小於號也可以。
#define
這個指令常被用來定義一個巨集(marco),巨集也稱為宏(中國大陸音譯)。
巨集是由 preprocessor 執行的文字替換機制。
Syntax :
1 |
如下範例,透過 #define 將 3.14159 這個常數用 PI 表示,PI 就是巨集,其後若出現的任何巨集都會被替換為 3.14159 常數。
1 |
|
#define 的四種語法
前面介紹的屬於下面的常數巨集。
- Constant Macros(常數巨集)
- Chain Macros(鏈式巨集)
- Macro Expressions(巨集運算式)
- Multiline Macros(多行巨集)
Chain Macros
簡單來說就是巨集再套一個巨集。
它的具體定義如下:
1 |
範例:
1 |
|
這支程式碼的作用主要就是 MA MB MC MD 這些巨集都可以代表 100 的意思。
可以無限串下去,只要 preprocessor 處理得來的話。
Macro Expressions
也稱為類函數巨集。
通常可接受參數並展開成一段運算式或是函數呼叫的程式碼片段。
Syntax :
1 |
or
1 |
範例:
1 |
|
然後也可以寫成這樣:
1 |
|
Multiline Macros
就是可以建立多行的巨集。
這部分要用到反斜線 \ 去做到這件事。
定義如下:
1 |
範例:
1 |
|
#undef
這指令用於取消先前用 #define 定義的巨集,使用方法也很簡單,如下:
1 | #undef macro_name |
範例:
1 |
|
輸出結果:
1 | main.cpp: In function ‘int main()’: |
條件編譯(Conditional Compilation)
以下這些指令都是條件預處理器指令:
#if#elif#else#endif#error
Syntax :
1 |
|
使用上與 C++ 的條件語句是差不多的,#elif 就是 else if,差別在於需要用 #endif 表示條件編譯的結束。
範例(https://www.geeksforgeeks.org/cpp/cpp-preprocessors-and-directives/):
1 |
|
輸出結果:
1 | PI is defined |
#ifdef & #ifndef
#ifdef 判斷巨集是否定義;#ifndef 有個 n,表示判斷巨集是否未定義。
Syntax :
1 |
|
範例:
1 |
|
輸出結果:
1 | 除錯模式 |
為什麼需要條件編譯?
在編譯前,可以選擇性包含或排除某些程式碼,使得編譯器只編譯所需部分的程式碼,這叫做條件編譯。
這樣做有什麼好處?
- 跨平台(Cross-platform)兼容性佳
- 節省資源
- 減小可執行檔(.exe)體積
#error
用來自訂編譯錯誤時的訊息。
Syntax :
1 | #error error_message |
範例改自 GeeksForGeeks:
1 |
|
輸出結果:
1 | main.cpp:12:10: error: #error "巨集 PI 或 SQUARE 沒被定義" |
#warning
在編譯前可以透過這個指令自訂先行警告訊息。
跟 #error 有什麼差別?#error 是自訂編譯錯誤時的訊息,#warning 會在編譯前警告。
Syntax :
1 | #warning message |
範例:
1 |
|
輸出結果:

需注意的是,警告訊息會由編譯器不同而有所不一樣的格式。
#pragma
#pragma 用於告訴編譯器針對特定需求執行特殊行為,其語法和功能並不屬於 C++ 語言標準,而是由各編譯器自行定義和支援。
在使用上需要看編譯器怎麼定義 #pragma 的行為,不然會因為不相容問題產生一些錯誤。
Syntax :
1 | #pragma directive |
以下是 #pragma 常用的 Flags:
#pragma once:用於保護 header files#pragma message:用於在編譯期間打印自訂訊息#pragma warning:用於控制警告行為(如啟用或停用警告)。#pragma optimize:用於控制最佳化設定(管理最佳化等級)。#pragma comment:用於在 .o 檔中含一些附加資訊(或指定 linker 選項)。
範例:
#pragma once 在以下範例中,就是要確保 MyHeader.h 不要被多次包含,只要包含一次就好。
1 | // MyHeader.h |
#pragma message 範例:
1 |
|
#pragma warning 用來開啟或關閉指定的編譯器警告(僅部分編譯器支援),以下是舉 MSVC 編譯器為例製作的範例:
1 |
|
要重新啟用就可這樣打:#pragma warning(default:4100)
#pragma optimize 範例(MSVC):
1 |
|
# 和 ## 運算子
# 運算子拿來字串化,就是把巨集中的參數轉字串。
範例:
1 |
|
輸出結果:
1 | Hello World |
以上範例的 STR 巨集透過 #x,可將輸入的引數 Hello World 變為字串 "Hello World"。
## 運算子則是把巨集中的兩個參數連接(concatenate)成一個識別字。
範例:
1 |
|
輸出結果:
1 | 123 |
可以看到真的可以拿來作為識別字,這個在對於生成函數跟變數名稱會很有用。
總結
前置處理器作用
用於在編譯器處理之前,先對程式碼進行「展開與清理」。
功能:
- 引入標頭檔
- 條件編譯
- 文字替換
- 移除註解等
前置處理後的程式碼稱為「展開後程式碼」,通常存為 .i 檔案。
前置處理指令以 # 開頭,非 C++ 語法,不需要分號。
常見指令與功能
#include:引入其他檔案(常用於 header files)。
#define:定義巨集(文字替換),包含:
常數巨集
- 鏈式巨集(巨集套巨集)
- 巨集運算式(類函數巨集,可傳參數)
- 多行巨集(可用反斜線延伸多行)
#undef:取消已定義的巨集。
條件編譯
指令:#if、#elif、#else、#endif、#ifdef、#ifndef。
透過根據巨集是否定義,或條件是否成立,來選擇性保留或排除程式碼。
優點:
- 跨平台兼容
- 節省資源
- 縮小可執行檔體積
#error:自訂錯誤訊息,強制中斷編譯。
#warning:在編譯前顯示警告訊息。
#pragma:提供編譯器專屬的特殊指令,非標準 C++。
常用 flags:
#pragma once:避免標頭檔重覆引入。
#pragma message:編譯期間顯示提示訊息。
#pragma warning:控制警告行為。
#pragma optimize:調整最佳化設定。
運算子
#:字串化,把巨集參數轉成字串。
##:連接參數,用於生成識別字(如動態生成變數或函數名稱)。
參考資料
[C 語言] 程式設計教學:如何使用巨集 (macro) 或前置處理器 (Preprocessor) | 開源技術教學
C++ Preprocessor And Preprocessor Directives - GeeksforGeeks


