1. 簡單物件式巨集
#define IDENTIFIER replacement-list
// replacement-list 可為:數值、字串字面值、標點、運算子、識別字、關鍵字、字元常數、空
經典錯誤與修正
| 錯誤寫法 | 展開結果 | 問題 | 正確寫法 |
#define N = 100 | int a[= 100]; | = 成為標記一部分 | #define N 100 |
#define N 100; | int a[100;]; | 分號被包含在替換文本中 | #define N 100 |
#define pin (int*)
pin a, b; | int* a, b; | b 是 int 非指標 | typedef int* PIN;
PIN a, b; |
創意用法 (不建議生產碼)
#define BEGIN {
#define END }
#define LOOP for(;;)
#define IT int
int main() BEGIN
LOOP
IT i = 0;
break;
END
END
2. 帶參數函數式巨集
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define IS_EVEN(n) ((n) % 2 == 0)
i = MAX(j + k, m - n);
// 安全展開:((j + k) > (m - n) ? (j + k) : (m - n))
if (IS_EVEN(i)) i++;
// 展開:if (((i) % 2 == 0)) i++;
關鍵規則
#define NAME(params) ... 中 NAME 與 ( 不能有空格,否則視為物件式巨集。
- 參數在替換列表中完全展開後再代入 (遞迴展開),順序無關。
- 參數加括號、整體加括號:防止運算子優先級錯誤。
3. 特殊運算子:# 字串化、## 連接
#define STR(x) #x // 字串化
#define CAT(a, b) a##b // 連接
#define CHAR(x) #@x // 字元字面值化 (Microsoft 擴展,非標準)
STR(hello) → "hello"
CAT(var, 1) → var1
CHAR(1) → '1' (MSVC)
進階連接技巧
#define MAKE_FUNC(name) int func_##name(void) { return 0; }
MAKE_FUNC(init) → int func_init(void) { return 0; }
#define DECLARE_ARRAY(type, name, size) type name##_arr[size]
DECLARE_ARRAY(int, buffer, 1024) → int buffer_arr[1024];
4. 多行巨集與 do { ... } while(0) 慣用語
#define SAFE_FREE(ptr) do { \
if (ptr) { \
free(ptr); \
ptr = NULL; \
} \
} while(0)
// 使用:SAFE_FREE(p); // 結尾有分號,語法正確
// 若用 if/else 包裹會破壞 else 綁定;do-while(0) 解決此問題
5. 條件編譯:跨平台開發核心武器
#ifdef _WIN32
#include
#define SLEEP(ms) Sleep(ms)
#elif defined(__linux__)
#include
#define SLEEP(ms) usleep((ms)*1000)
#else
#error "Unsupported platform"
#endif
// 編譯時指定:gcc -D_WIN32 ...
6. 取消巨集
#define DEBUG 1
// ... code ...
#undef DEBUG
// 之後 DEBUG 未定義
7. 預定義巨集 (標準 C)
| 巨集 | 含義 | 類型 |
__LINE__ | 當前原始碼行號 | 整數 |
__FILE__ | 當前原始檔名稱 | 字串 |
__DATE__ | 編譯日期 (Mmm dd yyyy) | 字串 |
__TIME__ | 編譯時間 (hh:mm:ss) | 字串 |
__STDC__ | 符合標準 C 則為 1 | 整數 |
__STDC_VERSION__ | C 版本 (199409L, 199901L, 201112L) | 整數 |
__cplusplus | C++ 編譯器版本 | 整數 |
實用除錯巨集範例
#define CHECK_ZERO(divisor) \
do { \
if ((divisor) == 0) { \
fprintf(stderr, "*** DIVIDE BY ZERO at %s:%d ***\n", __FILE__, __LINE__); \
abort(); \
} \
} while(0)
int div(int a, int b) {
CHECK_ZERO(b);
return a / b;
}
8. 巨集最佳實踐總結
- 巨集名稱全大寫,與變數/函數區分。
- 複雜邏輯優先用 inline 函數 (型別安全、除錯可見、避免副作用)。
- 巨集參數必須加括號,整體必須加括號。
- 避免巨集參數有副作用:
MAX(i++, j++) 導致 i 增加兩次。
- 多行巨集使用
do { ... } while(0) 包裹。
- 條件編譯管理平台差異、功能開關、除錯碼。