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