宏定義是我們C語言學習中非常重要的內容。一些基礎的用法大家都比較清楚了,我們簡單總結一下。
1.宏定義的格式為:#define 標識符 字符串。
2.宏定義屬于預處理命令,在編譯過程中的預處理階段處理。
3.宏定義只是單純的替換,所以當被替換內容涉及運算等的時候好加上括號()。
4.宏定義的標示符一般用大寫。
5.宏定義的標示符為常量標示符,即不可再賦值。
6.宏定義末尾不加分號。
以上說的是宏定義的近本用法,可以帶來很多好處。比如讓我們的標示符有意義,讓我們的代碼修改更方便,可以替代在代碼中常用的字符串縮短代碼等。其實在宏定義中,我們也可以像一個“函數”一樣實現一個的功能,這種用法叫函數宏,函數宏在我們對宏定義的使用中更加的常江,下面我們從五個方面來了解下函數宏的使用。
1.函數宏的書寫
#defineMAX(a,b)((a)(b)?(a):(b)),這就是一個簡單的函數宏,我們同樣可以傳遞參數,實現功能。但是在書寫上注意兩點MAX和左“(”之間沒有空格,因為宏定義把標示符后的第一個空格會認為是標示符與字符串的分割。當然我們在寫宏的時候有時候會寫多行,這樣我們一般用“\”進行分割。
2.加括號
我們說到宏只是簡單的替換,即使是函數宏也是這樣的,所以為了避免一些優先級的錯誤不要忘記加括號。
3.宏的副作用
這也是函數宏和函數不同的地方。比如上邊的例子 #define MAX(a,b) ((a)>(b)?(a):(b))我們傳入的參數是++a和++b。很顯然如果使用函數實現這個功能的話a和b均自加一次,但是如果用宏實現替換后就變成((++a)>(++b)?(++a):(++b)),很明顯,這與函數就完全不同了。
4.do{}while(0)結構
例如:
#define DELETE_POINTER(p) \
do \
{ \
if(NULL != p) \
delete p; \
p = NULL; \
}while(0)
這種結構在函數宏里非常常見,它不僅可以在調用后加分號保持代碼的格式一致性,還可以避免一些復雜的宏定義產生的錯誤。當然,每行后面不要忘了也是需要”\”的。
5.函數宏中的#和##運算符
在函數宏中#可以實現由函數宏實參生成字符串常量,##實現了由函數宏實參生成標識符的一部分。(前者用于拼接字符串后者用于拼接標示符)看一下下邊的示例:
#
假如希望在字符串中包含宏參數,ANSIC允許這樣作,在類函數宏的替換部分,#符號用作一個預處理運算符,它可以把語言符號轉化程字符串。例如,如果x是一個宏參量,那么#x可以把參數名轉化成相應的字符串。該過程稱為字符串化(stringizing)。
#incldue
#define PSQR(x) printf("the square of" #x "is %d.\n",(x)*(x))
int main(void)
{
int y =4;
PSQR(y);
PSQR(2+4);
return 0;
}
輸出結果:
the square of y is 16.
the square of 2+4 is 36.
第一次調用宏時使用“y”代替#x;第二次調用時用“2+4"代#x。
##
##運算符可以使用類函數宏的替換部分。另外,##還可以用于類對象宏的替換部分。這個運算符把兩個語言符號組合成單個語言符號。例如:
#define XNAME(n) x##n
這樣宏調用:
XNAME(4)
展開后:x4
程序:
#include
#define XNAME(n) x##n
#define PXN(n) printf("x"#n" = %d\n",x##n)
int main(void)
{
int XNAME(1)=12;//int x1=12;
PXN(1);//printf("x1 = %d\n", x1);
return 0;
}
那么說了這么多,大家一定有疑問,函數宏和函數的區別又有什么呢?我們把上面第一條的例子用函數來實現:
int max( int a, int b)
{
return (a > b a : b)
}
如果這段代碼要頻繁使用,讓我們選擇用函數宏或者用函數來實現。很顯然,我們不會選擇用函數來完成這個任務,原因有兩個:首先,函數調用會帶來額外的開銷,它需要開辟一片棧空間,記錄返回地址,將形參壓棧,從函數返回還要釋放堆棧。這種開銷不僅會降低代碼效率,而且代碼量也會大大增加,而使用宏定義則在代碼規模和速度方面都比函數更勝一籌;其次,函數的參數必須被聲明為一種特定的類型,所以它只能在類型合適的表達式上使用,我們如果要比較兩個浮點型的大小,就不得不再寫一個專門針對浮點型的比較函數。反之,上面的那個宏定義可以用于整形、長整形、單浮點型、雙浮點型以及其他任何可以用“>”操作符比較值大小的類型,也就是說,宏是與類型無關的。和使用函數相比,使用宏的不利之處在于每次使用宏時,一份宏定義代碼的拷貝都會插入到程序中。除非宏非常短,否則使用宏會大幅度增加程序的長度。還有一些任務根本無法用函數實現,但是用宏定義卻很好實現。比如參數類型沒法作為參數傳遞給函數,但是可以把參數類型傳遞給帶參的宏。
看下面的例子:
#define MALLOC(n, type) \
( (type *) malloc((n)* sizeof(type)))
利用這個宏,我們就可以為任何類型分配一段我們指定的空間大小,并返回指向這段空間的指針。我們可以觀察一下這個宏確切的工作過程:
int *ptr;
ptr = MALLOC ( 5, int );
將這宏展開以后的結果:
ptr = (int *) malloc ( (5) * sizeof(int) );
這個例子是宏定義的經典應用之一,完成了函數不能完成的功能。
把類型作為函數宏參數是C語言實現泛型的一種手段,這也是函數宏常用的場合之一。在后續出現的編程語言入C++中把這種需求作為一種新的語法特性(模板)加以實現。
我們主要介紹了函數宏的用法,熟練的掌握還需要更多的練習,希望在以后代碼編程過程中可以將函數宏融入我們的代碼,使我們的代碼水平不斷提高。