在 C 語言中,#define 是預處理器指令,用於定義巨集。它能提升程式碼可讀性、便於維護,並在某些情況下提高執行效率。然而,許多初學者對其本質理解不足,導致誤用或誤讀他人程式碼。本文將從巨集定義的基本概念、替換時機、常見陷阱、優點,以及進階用法(如 Token Pasting、Stringizing、GCC Statement Expression)進行系統性解析。
#define 將一個識別符(巨集名)定義為一個字串(替換文本)。主要分兩種形式:
#define <巨集名> <字串>#define PI 3.1415926#define <巨集名>(<參數表>) <巨集體>#define SQUARE(x) ((x)*(x))關鍵點:巨集替換發生在預處理階段,純粹是文字替換,不涉及任何運算或語法檢查。
C 程式編譯流程:原始碼 → 預處理器 → 修改後的原始碼 → 編譯器 → 組譯器 → 連結器 → 執行檔。
預處理器負責三大任務:檔案包含(#include)、條件編譯(#if/#ifdef)、巨集展開。此階段僅做字串替換與展開,無計算功能。理解這點是避免誤用的關鍵。
#define N 2+2
int a = N * N; // 預期 16,實際 8
原因:預處理器將 N 替換為 2+2,程式變為 a = 2+2*2+2,依運算子優先級先乘後加,結果為 8。
解決:為替換文本加括號:#define N (2+2),展開後為 (2+2)*(2+2) = 16。
#define SQUARE(x) x*x
int y = SQUARE(2+2); // 預期 16,實際 8
int z = SQUARE(2+2) / SQUARE(2+2); // 預期 1,實際 16
原因:參數 2+2 直接替換 x,導致 2+2*2+2;第二例展開為 (2+2)*(2+2)/(2+2)*(2+2),依左結合性計算得 16。
最佳實踐:
#define SQUARE(x) ((x)*(x))#define SQUARE(x) ((x)*(x))(已包含)SQUARE(i++) 會導致 i 遞增兩次)。#define PI 3.1415926。## 運算子可將兩個 Token 連接成一個。
#define CONCAT(x, y) x##y
int n = CONCAT(123, 456); // n = 123456
char *s = CONCAT("abc", "def"); // s = "abcdef"
應用例:MFC 中 BEGIN_INTERFACE_PART(localClass, baseClass) 定義為 class X##localClass : public baseClass,自動生成類名 XlocalClass。
# 運算子將參數轉為字串常值(雙引號)。
#define TO_STRING(x) #x
char *s = TO_STRING(123); // s = "123"
#@ 將參數轉為字元常值(單引號),僅支援單一字元。
#define TO_CHAR(x) #@x
char c = TO_CHAR(1); // c = '1'
char c2 = TO_CHAR(123); // c2 = '3' (取最後一字元),超過 4 字元編譯錯誤
標準 C 巨集無法包含複合陳述並回傳值。GCC 擴充允許:
#define MAX(a,b) ({ typeof(a) _a = (a); typeof(b) _b = (b); _a > _b ? _a : _b; })
或用於多語句巨集並回傳最後一個運算式的值:
#define DOIT(a,b,c) ({ a=1; b+=1; c=3; a+b+c; })
int d = DOIT(a,b,c); // d = 6
優點:避免巨集展開時需加分號導致的語法錯誤(如 if (...) DOIT(...); else ...)。
掌握 #define 的核心在於理解預處理階段純文字替換這一本質。遵循「參數加括號、整體加括號」原則,善用 ##、# 等進階運算子,即可安全發揮巨集的便利性與效能優勢。