宏定義在 C/C++ 開發中佔據核心地位,尤其在底層框架、跨平台適配、編譯優化等場景不可或缺。本文循序漸進,從對象宏與函數宏的基本概念入手,通過 MIN 宏的演進、NSLog 增強、ReactiveCocoa 中的 RACObserve 等實戰案例,揭示宏定義背後的「黑魔法」,助你讀懂、寫出健壯、優雅的宏。
定義常量,如 #define M_PI 3.14159265358979323846。預處理階段單純進行標記替換。
接受參數,行為類似函數。例如:
#define SELF(x) x
#define PLUS(x,y) x + y
調用時將實參代入定義體,再替換調用處。看似簡單,實則暗藏優先級、副作用、變量遮蔽等陷阱。
#define MIN(A,B) A < B ? A : B
問題:2 * MIN(3,4) 展開為 2 * 3 < 4 ? 3 : 4,乘法優先級高於比較,導致邏輯錯誤。
#define MIN(A,B) (A < B ? A : B)
解決外層優先級,但 MIN(3, 4 < 5 ? 4 : 5) 仍因內部運算符優先級相同導致計算順序錯亂。
#define MIN(A,B) ((A) < (B) ? (A) : (B))
修正嵌套表達式優先級,但引入副作用:MIN(a++, 1.5f) 導致 a++ 執行兩次。
#define MIN(A,B) ({ \
__typeof__(A) __a = (A); \
__typeof__(B) __b = (B); \
__a < __b ? __a : __b; \
})
將參數先賦值給臨時變量,消除副作用,並保留類型泛化能力。
#define __NSX_PASTE__(A,B) A##B
#define MIN(A,B) __NSMIN_IMPL__(A,B,__COUNTER__)
#define __NSMIN_IMPL__(A,B,L) ({ \
__typeof__(A) __NSX_PASTE__(__a,L) = (A); \
__typeof__(B) __NSX_PASTE__(__b,L) = (B); \
(__NSX_PASTE__(__a,L) < __NSX_PASTE__(__b,L)) ? __NSX_PASTE__(__a,L) : __NSX_PASTE__(__b,L); \
})
利用 ## 拼接 __COUNTER__ 生成唯一變量名,徹底避免名稱衝突。
標準 NSLog 輸出缺少文件、行號、函數名。利用可變參數宏、do-while(0) 包裹代碼塊、##__VA_ARGS__ 吞噬多餘逗號,實現增強版:
#define NSLog(format, ...) do { \
fprintf(stderr, "<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \
__LINE__, __func__); \
(NSLog)((format), ##__VA_ARGS__); \
fprintf(stderr, "-------\n"); \
} while (0)
__FILE__、__LINE__、__func__ 為預定義宏,提供定位信息。do-while(0) 確保宏在 if-else 中正確展開,吞噬尾隨分號。##__VA_ARGS__ 在可變參數為空時自動移除前置逗號,保證語法合法。利用 ({...})、@try/@catch、!! 雙重取反轉 BOOL、# 字符串化運算符,實現豐富的失敗上下文捕獲。
結合 metamacro 系列宏,在編譯期檢查鍵路徑合法性,生成 KVC 觀察信號。展開過程涉及參數計數、條件編譯、標記拼接等高階技巧,是宏元編程的典範。
## 拼接、# 字符串化、__VA_ARGS__ 可變參數、do-while(0) 代碼塊包裹、GNU 語句表達式等進階特性。
