C語言中文網 目錄
首頁 > 編程筆記 > C語言筆記 閱讀:12,940

宏定義(無參宏定義和帶參宏定義),C語言宏定義詳解

宏定義是比較常用的預處理指令,即使用“標識符”來表示“替換列表”中的內容。標識符稱為宏名,在預處理過程中,預處理器會把源程序中所有宏名,替換成宏定義中替換列表中的內容。

常見的宏定義有兩種,不帶參數的宏定義帶參數的宏定義

無參宏定義

無參數宏定義的格式為:

#define 標識符 替換列表

替換列表可以是數值常量、字符常量、字符串常量等,故可以把宏定義理解為使用標識符表示一常量,或稱符號常量。

說明:
1) # 可以不在行首,但只允許它前面有空格符。例如:
#define PI 3.1416 //正確,該行#前允許有空格
int a;#define N 5 //錯誤,該行#前不允許有空格外的其他字符

2) 標識符和替換列表之間不能加賦值號 =,替換列表后不能加分號
#define N =5 //雖語法正確,但預處理器會把N替換成=5
int a[N]; //錯誤,因為宏替換之后為 int a[=5];
宏定義不是語句,是預處理指令,故結尾不加分號。如果不小心添加了分號,雖然有時該宏定義沒問題,但在宏替換時,可能導致 C 語法錯誤,或得不到預期結果。例如:
#define N 5; //雖語法正確,但會把N替換成5;
int a[N]; //語法錯誤,宏替換后,為int a[5;];錯誤

3) 由于宏定義僅是做簡單的文本替換,故替換列表中如有表達式,必須把該表達式用括號括起來,否則可能會出現邏輯上的“錯誤”。例如:
#define N 3+2
int r=N*N;
宏替換后為:
int r=3+2*3+2; //r=11
如果采用如下形式的宏定義:
#define N (3+2)
int r=N*N;
則宏替換后,為:
int r=(3+2)*(3+2); //r=25

4) 當替換列表一行寫不下時,可以使用反斜線\作為續行符延續到下一行。例如:
#define USA "The United \
States of \
America"
該宏定義中替換列表為字符串常量,如果該串較長,或為了使替換列表的結構更清晰,可使用續行符 \ 把該串分若干行來寫,除最后一行外,每行行尾都必須加續行符 \。

如果調用 printf 函數,以串的形式輸出該符號常量,即:
printf("%s\n",USA);
則輸出結果為:The United States of America

注意:續行符后直接按回車鍵換行,不能含有包括空格在內的任何字符,否則是錯誤的宏定義形式。

帶參宏定義

帶參數的宏定義格式為:

#define 標識符(參數1,參數2,...,參數n) 替換列表

例如,求兩個參數中最大值的帶參宏定義為:
#define MAX(a,b) ((a)>(b)?(a) : (b))
當有如下語句時:
int c=MAX(5,3);
預處理器會將帶參數的宏替換成如下形式:
int c=((5)>(3)?(5) : (3));
故計算結果c=5。

刪除宏定義的格式為:

#undef 標識符

說明:
1) 標識符與參數表的左括號之間不能有空格,否則預處理器會把該宏理解為普通的無參宏定義,故以下是錯誤的帶參宏定義形式。
#define MAX (a,b) ( (a) > (b) ? (a) : (b) ) //錯誤的帶參宏定義格式
2) 宏替換列表中每個參數及整個替換列表,都必須用一對小括號 () 括起來,否則可能會出現歧義。

【例 1】以下程序試圖定義求兩個參數乘積的宏定義,欲使用該宏求 3 與 6 的乘積,分析該程序能否實現預期功能,如果不能,請給出修改方案。
#include <stdio.h>
#define MUL(a,b) (a*b)
int main (void)
{
    int c;
    c=MUL(3,5+1);
    printf("c=%d\n",c);
    return 0;
}
分析:
1) 由于該宏定義中的替換列表中的參數沒有加括號,故宏調用時,如果參數是個表達式,可能會出現歧義,得不到預期結果。

本例中宏調用 c=MUL(3,5+1); 會替換成 c=(3*5+1)=16;,與預期功能不符。

2) 雖然把宏調用時的參數 5+1 括起來,可達到題目要求的效果,但這屬于治標不治本。為統一編程規范,把替換列表中的每個參數均加括號,整個替換列表也加括號。

同時,為達到標本兼治,在宏定義時,除單一值參數外,應顯式加括號。

修改代碼為:
#include <stdio.h>
#define MUL(a,b) ((a)*(b))//修改處1
int main (void)
{
    int c;
    c=MUL(3,(5+1);//修改處2
    printf("c=%d\n",c);
    return 0;
}

帶參宏定義 VS 函教調用

接下來將從調用發生時間、參數類型檢查、參數是否需要空間、運行速度等幾個主要方面進行對比分析帶參宏定義與函數調用的差異。

調用發生的時間

在源程序進行編譯之前,即預處理階段進行宏替換;而函數調用則發生在程序運行期間。

參數類型檢查

函數參數類型檢查嚴格。程序在編譯階段,需要檢查實參與形參個數是否相等及類型是否匹配或兼容,若參數個數不相同或類型不兼容,則會編譯不通過。

在預處理階段,對帶參宏調用中的參數不做檢查。即宏定義時不需要指定參數類型,既可以認為這是宏的優點,即適用于多種數據類型,又可以認為這是宏的一個缺點,即類型不安全。故在宏調用時,需要程序設計者自行確保宏調用參數的類型正確。

參數是否需要空間

函數調用時,需要為形參分配空間,并把實參的值復制一份賦給形參分配的空間中。而宏替換,僅是簡單的文本替換,且替換完就把宏名對應標識符刪除掉,即不需要分配空間。

執行速度

函數在編譯階段需要檢查參數個數是否相同、類型等是否匹配等多個語法,而宏替換僅 是簡單文本替換,不做任何語法或邏輯檢查。

函數在運行階段參數需入棧和出棧操作,速度相對較慢。

代碼長度

由于宏替換是文本替換,即如果需替換的文本較長,則替換后會影響代碼長度;而函數不會影響代碼長度。

故使用較頻繁且代碼量較小的功能,一般采用宏定義的形式,比采用函數形式更合適。前面章節頻繁使用的 getchar(),準確地說,是宏而非函數。

為了使該宏調用像函數調用,故把該宏設計成了帶參數的宏定義:

#define getchar() getc(stdin)

故調用該宏時,需要加括號,即傳空參數:getchar()。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo