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

C語言預處理器詳解(一):宏定義與無參宏

     編輯:C 語言基礎

預處理器概覽

預處理是編譯前的文本處理階段,由預處理器(cpp)自動完成。以 # 開頭的指令不屬於 C 語法,不以分號結束。核心功能:宏定義、文件包含、條件編譯。合理使用可大幅提升代碼可讀性、可移植性與模塊化程度。

宏定義機制

宏定義建立標識符 → 替換文本的映射。預處理階段對源碼中所有出現的宏名進行宏展開(文本替換),隨後才進入編譯器詞法/語法分析。

無參宏定義

#define IDENTIFIER replacement-text
  • IDENTIFIER:宏名,慣用全大寫。
  • replacement-text:任意字符序列(常數、表達式、代碼片段、甚至多行)。
  • 替換是原樣文本替換,不做語法檢查、不求值。

示例:符號常量

#define PI 3.1415926
#define MAX_SIZE 1024
#define DEBUG 1

示例:表達式宏(括號至關重要)

#define SQUARE(x) ((x)*(x))   // 帶參宏見下文

// 無參表達式宏
#define AREA_CIRCLE (PI * r * r)  // 危險:依賴外部變量 r

// 較安全:將表達式括起來
#define EXPR (y*y + 3*y)

宏展開實例

#define M (y*y + 3*y)

int s = 3*M + 4*M + 5*M;
// 展開後:
int s = 3*(y*y + 3*y) + 4*(y*y + 3*y) + 5*(y*y + 3*y);

// 若定義無括號:#define M y*y + 3*y
// 展開:3*y*y + 3*y + 4*y*y + 3*y + 5*y*y + 3*y  -> 錯誤結果

宏定義最佳實踐:括號三原则

  1. 整體外層加括號:#define EXPR ( ... ) 防止優先級干擾。
  2. 參數兩側加括號:#define SQUARE(x) ((x)*(x)) 防止傳入表達式時運算順序錯亂。
  3. 多語句宏用 do-while(0) 包裹(見帶參宏章節)。

經典錯誤演示

#define SQ(y) (y)*(y)
int sq = 160 / SQ(a+1);  // a=3
// 展開:160 / (a+1)*(a+1) = 160/4*4 = 160  (預期 10)

// 修正:#define SQ(y) ((y)*(y))
// 再修正:#define SQ(y) (((y)*(y)))  // 整體再括一次

宏 vs 函數:本質區別

維度宏函數
處理階段預處理(文本替換)編譯/鏈接(代碼生成)
類型檢查無有
參數求值不求值,原樣替換求值後傳值(值傳遞)
副作用參數多次展開導致多次求值參數求值一次
調試不可單步調試,行號混亂完整調試支持
代碼膨脹每次調用展開,增加體積單一實體,調用開銷固定
遞歸不支持支持

副作用災難案例

#define SQ(y) ((y)*(y))

int i = 1;
while (i <= 5) {
    printf("%d\n", SQ(i++));  // i++ 展開兩次!
}
// 展開:((i++)*(i++))
// 輸出:2, 12, 30 ... 而非 1,4,9,16,25

多語句宏定義

#define SSSV(s1,s2,s3,v)  s1=l*w; s2=l*h; s3=w*h; v=w*l*h;
// 調用:SSSV(sa,sb,sc,vv);
// 展開為 4 賦值語句

缺陷:若放在 if 後無大括號,只執行第一句。標準解法見下章帶參宏。

總結

  • 宏是文本替換,非代碼執行,無類型、無作用域、無調試。
  • 無參宏用於符號常量、簡單表達式片段。
  • 括號是宏的生命線:參數括、表達式括、整體括。
  • 能用 const/enum/inline 函數替代宏時,優先不用宏。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved