# 字串化運算子在類函式巨集替換部份,# 將巨集參數轉換為字串常值。過程稱「字串化」。
#define PSQR(x) printf("the square of " #x " is %d.\n", (x)*(x))
PSQR(y); // 展開:printf("the square of " "y" " is %d.\n", (y)*(y));
// 輸出:the square of y is 16.
PSQR(2+4); // 展開:printf("the square of " "2+4" " is %d.\n", (2+4)*(2+4));
// 輸出:the square of 2+4 is 36.
參數名 y 變成字串 "y",運算式 2+4 變成字串 "2+4"。相鄰字串常值自動合併。
## 符號連接運算子將兩個語言符號 (Token) 合併為單一符號。可用於類函式巨集與類物件巨集。
#define XNAME(n) x##n
XNAME(4) // 展開:x4
#define PXN(n) printf("x" #n " = %d\n", x##n)
int XNAME(1) = 12; // int x1 = 12;
PXN(1); // printf("x1 = %d\n", x1);
// 輸出:x1 = 12
應用場景:自動生成變數名、函式名、欄位名,構建輕量級元程式設計。
__VA_ARGS__ 可變參數巨集 (C99)巨集參數列表最後用 ... 表示可變參數,替換部份用 __VA_ARGS__ 承接。
#define PR(...) printf(__VA_ARGS__)
int main() {
int wt=1, sp=2;
PR("hello\n"); // printf("hello\n");
PR("weight = %d, shipping = %d", wt, sp); // printf("weight = %d, shipping = %d", wt, sp);
return 0;
}
// 標準 C99:呼叫時至少需一個固定參數
#define LOG(fmt, ...) printf(fmt, __VA_ARGS__)
// LOG("test"); // 錯誤:__VA_ARGS__ 為空,殘留逗號
// GCC 擴展:##__VA_ARGS__ 自動吞噬前置逗號
#define LOG(fmt, ...) printf(fmt, ##__VA_ARGS__)
LOG("test"); // 正常:展開為 printf("test")
MSVC (Visual Studio) 2015+ 也支援 __VA_ARGS__,但 ##__VA_ARGS__ 行為略有不同,需測試驗證。
#ifdef DEBUG
#define DBG_PRINT(fmt, ...) \
do { \
fprintf(stderr, "[DEBUG][%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
} while(0)
#else
#define DBG_PRINT(fmt, ...) ((void)0)
#endif
// 使用
DBG_PRINT("User %s logged in, id=%d", username, uid);
do { ... } while(0) 確保巨集在語法上像單一語句,可安全用於 if/else 後。__FILE__, __LINE__ 定位來源碼位置。| 運算子 | 功能 | 適用範圍 | 關鍵點 |
|---|---|---|---|
# | 參數字串化 | 類函式巨集 | 參數名 → "參數名" |
## | 符號連接 | 類函式/類物件巨集 | TokenA ## TokenB → TokenAB |
__VA_ARGS__ | 可變參數承接 | 類函式巨集 (C99) | 配合 ... 使用,注意逗號處理 |
掌握這三大預處理利器,能編寫出極具表達力、類型安全、零執行期開銷的除錯、日誌、序列化等基礎設施巨集。