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

C/C++ 預處理器進階技巧:## 連接符與 # 字串化實戰

     編輯:VC 教學

前言

巨集不僅是簡單的文字替換,## (Token Pasting) 與 # (Stringification) 兩大預處理運算子,能在編譯期構建識別碼、生成字串字面值,是 ATL、MFC、Linux Kernel 等大型專案廣泛使用的元編程利器。

## 連接符

功能:將兩個 Token 合併為單一 Token。限制:不能出現在巨集參數列表的首尾。

經典範例

#define PASTER(n) printf("token" #n " = %d", token##n)

int token9 = 9;
PASTER(9);

展開過程:

  1. #n → "9" (字串化)
  2. token##n → token9 (連接)
  3. 最終:printf("token" "9" " = %d", token9); → 輸出 token9 = 9

# 字串化運算子

將巨集參數轉為字串字面值,保留原始拼寫。常用於診斷輸出、序列化標籤生成。

實戰案例:ATL IMPLEMENTS_INTERFACE

#define IMPLEMENTS_INTERFACE(Itf) \
    { &IID_##Itf, ENTRY_IS_OFFSET, BASE_OFFSET(_ITCls, Itf) },

使用 IMPLEMENTS_INTERFACE(ICat) 時,IID_##Itf 展開為 IID_ICat,自動對應介面 GUID。這避免了手寫重複代碼,並確保介面名稱與 GUID 變數命名一致性。

Linux Kernel 怪圈:do { ... } while(0)

#define wait_event(wq, condition) \
    do { \
        if (condition) break; \
        __wait_event(wq, condition); \
    } while (0)

為何用 do { } while(0)?

  • 將多語句包裝為單一語句,安全用於 if (...) wait_event(...); else ... 而不改變控制流。
  • 內部可使用 break 提前退出,模擬函數返回行為。
  • 編譯器會優化掉無意義循環,零開銷。

進階組合技:# + ## 協同

#define DECLARE_EVENT_HANDLER(EventName) \
    void On##EventName(); \
    static const char* Get##EventName##Name() { return #EventName; }

// 使用
DECLARE_EVENT_HANDLER(Click)
// 展開為:
// void OnClick();
// static const char* GetClickName() { return "Click"; }

避坑指南

  • 連接後必須形成合法 Token:## 不能產生關鍵字、非法標識符。
  • 參數副作用:# 與 ## 會阻止參數的進一步展開,若需先展開再連接,需雙層巨集:
    #define CAT(a,b) a##b
    #define EXPAND_CAT(a,b) CAT(a,b)
  • 可讀性:複雜巨集建議拆分為多層、加註釋,或改用 C++17 constexpr/模板元編程替代。

總結

掌握 ## 與 #,能讓你讀懂 ATL/MFC/Linux 核心巨集庫,也能編寫極具表達力的編譯期代碼生成器。但在現代 C++ 中,請優先考慮 constexpr、std::string_view、模板別名等類型安全方案,僅在跨平台 C 相容、極致效能、或舊代碼維護場景下使用預處理器黑魔法。

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