首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

C/C++ 預處理器神器:# 與 ## 運算符深度解析與實戰技巧

     編輯:關於C

C/C++ 預處理器提供兩個強大的標記運算符:#(字符串化)和 ##(標記粘連/連接)。它們讓宏能夠在預編譯期操作標記本身,實現元編程能力,廣泛用於日誌、序列化、代碼生成等場景。

運算符語義

運算符名稱功能位置
#字符串化將宏參數轉換為字符串字面量宏定義體中,僅能作用於參數
##標記粘連將兩側標記合併為單個標記宏定義體中,可作用於參數或普通標記

基礎示例

#define MZ_PRINT(n) printf("x" #n "=%d", x##n)

int x1 = 1;
MZ_PRINT(1);
// 預處理後展開為:
// printf("x" "1" "=%d", x1);
// 即:printf("x1=%d", x1);
  • #n → "1"(參數 1 字符串化)
  • x##n → x1(標記 x 與參數 1 粘連)
  • 相鄰字符串字面量自動合併:"x" "1" "=%d" → "x1=%d"

進階規則與陷阱

1. 參數預展開順序

## 兩側的參數不會先展開,直接粘連原始標記。若需先展開參數再粘連,需引入輔助宏:

#define CONCAT(a, b) a##b
#define EXPAND_CONCAT(a, b) CONCAT(a, b)

#define FOO 123
EXPAND_CONCAT(FOO, BAR) // → 123BAR
CONCAT(FOO, BAR)        // → FOOBAR (參數未展開)

2. 字符串化不展開參數內的宏


#define STR(x) #x
#define VAL 42
STR(VAL)      // → "VAL" 而非 "42"
STR(VAL+1)    // → "VAL+1"

同樣需雙層宏強制展開:


#define XSTR(x) STR(x)
XSTR(VAL)     // → "42"

3. 空參數處理

C99 可變參數宏 __VA_ARGS__ 配合 ## 可消除尾隨逗號:


#define LOG(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
LOG("hello")        // → fprintf(stderr, "hello")  // ## 吃掉逗號
LOG("val=%d", 42)   // → fprintf(stderr, "val=%d", 42)

4. 粘連結果必須是合法標記


#define PASTE(a, b) a##b
PASTE(123, 456)  // → 123456 (合法預處理數字)
PASTE(+, -)      // → +- (非法標記,編譯錯誤)

實戰應用模式

自動化重複代碼生成


#define DECLARE_GETTER(type, name) \
    type get_##name() const { return m_##name; }

class Foo {
    int m_id;
    float m_score;
public:
    DECLARE_GETTER(int, id)
    DECLARE_GETTER(float, score)
};
// 展開為 get_id(), get_score()

枚舉到字符串映射


#define ENUM_TO_STR(e) case e: return #e;
const char* error_str(int code) {
    switch(code) {
        ENUM_TO_STR(E_OK)
        ENUM_TO_STR(E_NOMEM)
        ENUM_TO_STR(E_INVAL)
        default: return "UNKNOWN";
    }
}

編譯期斷言(C11 前)


#define COMPILE_ASSERT(cond, msg) typedef char static_assert_##msg[(cond)?1:-1]
COMPILE_ASSERT(sizeof(int) == 4, int_must_be_32bit);

X-Macro 表驅動編程


#define OP_LIST \
    X(ADD, +) \
    X(SUB, -) \
    X(MUL, *)

#define X(name, op) name,
enum Op { OP_LIST };
#undef X

#define X(name, op) #op,
const char* op_str[] = { OP_LIST };
#undef X

調試技巧

  • GCC/Clang: gcc -E -P file.c 查看預處理輸出。
  • MSVC: cl /E /P file.c 生成 .i 文件。
  • IDE 通常提供「展開宏」功能(如 VS 的右鍵→預處理器→展開)。

總結

# 與 ## 是預處理器的「元編程雙子星」。善用它們能極大減少樣板代碼、實現領域特定語言(DSL)風格接口,但濫用會導致代碼難讀、難調試。原則:能用語言本身機制(模板、內聯函數、constexpr)解決時,優先不用宏;必須用宏時,保持定義簡潔、文檔完善、命名規範(如大寫+前綴)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved