程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> C語言入門知識 >> C語言筆記之宏定義

C語言筆記之宏定義

編輯:C語言入門知識

(一)符號常量

宏定義是C語言中的一種替換策略,即使用預處理命令 #define 將一串(冗長的)文本與某個名字(稱為宏)等同起來,然後就可以在源代碼中批量使用宏。在預處理階段再將源代碼中的宏替換為原來的文本。例如,在源代碼中:

 

#define  PI   3.14
那麼在接下來的代碼中,需要寫3.14的地方可以直接用PI代替。預處理的時候,PI又全部變回3.14。

 

這樣換來換去的有啥好處呢?萬一代碼的中的3.14需要全部改為3.1415926,那麼如果沒有剛才的宏定義,就只能挨個去修改;但是有了宏定義的話,只需修改宏定義就好了:

 

#define  PI   3.1415926

 

 

可以看出,每個#define命令由三部分組成,分別是:#define命令本身;縮略語(宏);替換文本或主體。各個部分之間用空格隔開,所以宏名稱中不能存在空格,且必須遵循C變量命名規則。宏名稱一般使用大寫字母。

預處理中,從宏變回主體的過程成為宏展開。有些宏定義的主體比較長,可以在行尾使用反斜線“\”將剩余的部分延伸到下一行(但是注意第二行如果沒有左對齊,那麼開頭的那些空白也會被當成主體的一部分)。


主體部分可以是常量,也可以是C表達式,甚至是一條完整的語句(即帶有分號),總之可以是任意字符串。這裡需要注意的是,如果主體中仍含有宏名稱,則該宏也會被替換;但是如果這個宏名在主體中被雙引號括起來,那麼就不會發生宏替換了,只會被按照字面意思理解。

 

(二)類函數宏

宏定義分為兩種,上面提到的都是不帶參數的,稱為類對象宏,這種宏也被稱為符號常量;另外還有一種帶參數的,稱為類函數宏。例如:

#define FUN(X)  X * X
類函數宏的外形與函數十分相似,也是在名稱後面緊跟一對圓括號,然後參數列表置於括號中。然後其主體就是這些參數的某種運算規則。在使用這個宏時,X可以被其他字符替換,就是函數的參數一樣,X起到的也是參數的作用。
代碼中這樣的代碼:

 

 

x = FUN(4);
會被替換成:

 

 

x= 4 * 4;
可能有點繞,但是我個人把這個宏展開的過程分為兩步理解:首先,把FUN(4)直接替換成X * X;然後再用“實參”4代替“形參”X(不知道機器是不是這樣執行的,會不會是偶的創新?)。

 

 

但是對類函數宏要有足夠的警惕:預處理器在宏展開時,僅僅進行文字替換操作,而不是真的像函數傳參那樣。比如剛才那個宏這樣使用:

 

x = FUN(3 + 4);
我們期望會替換成:
x = 7 * 7;
可實際上是:

 

 

x = 3 + 4 * 3 + 4;
由於結合順序的原因,我們不會得到想要的結果。避免出現這種結果的辦法就是:定義類函數宏時,最好給每個出現在主體中的參數加上括號,同時給每個運算規則也用括號保護起來。如,FUN應該這樣定義:

 

 

#define FUN(X)  ((X) * (X))
那麼替換之後就是這樣:

 

 

x = ((3 + 4) * (3 + 4))
這樣看似麻煩啰嗦,但卻是避免意外的好辦法。

下面是搬運時間(人家寫的太精彩了,我就可恥的偷把懶~,摘自《Linux C一站式編程》)
函數式宏定義經常寫成這樣的形式(取自內核代碼 include/linux/pm.h ):
#define device_init_wakeup(dev,val) \
do { \
device_can_wakeup(dev) = !!(val); \
device_set_wakeup_enable(dev,val); \
} while(0)
為什麼要用 do { ... } while(0) 括起來呢?不括起來會有什麼問題呢?
#define device_init_wakeup(dev,val) \
device_can_wakeup(dev) = !!(val); \
device_set_wakeup_enable(dev,val);
if (n > 0)
device_init_wakeup(d, v);
這樣宏展開之後,函數體的第二條語句不在 if 條件中。那麼簡單地用 { ... } 括起來組成一個語
句塊不行嗎?
#define device_init_wakeup(dev,val) \
{ device_can_wakeup(dev) = !!(val); \
device_set_wakeup_enable(dev,val); }
if (n > 0)
device_init_wakeup(d, v);
else
continue;
問題出在 device_init_wakeup(d, v); 末尾的 ; 號,如果不允許寫這個 ; 號,看起來不像個函數調用,可如果寫了這個 ; 號,宏展開之後就有語法錯誤, if 語句被這個 ; 號結束掉了,沒法
跟 else 配對。因此, do { ... } while(0) 是一種比較好的解決辦法。
然後我做點補充,do { ... } while(0) 這種形式到底怎麼運行?我們知道,這種do...while()循環稱為退出條件循環,判斷條件在執行循環之後進行檢查,這樣就可以保證循環體中的語句至少被執行一次,而且這裡的while條件被置為0,也就是說循環體恰好只執行一次,真是聰明的辦法!!

 

 

(三)#與##運算符

寫到這裡真的有點撐不住,妹的C語言也真是博大精深,一個宏定義都要搞得這麼復雜。。。

上面提到過,主體中雙引號內的宏名稱不會發生替換而是被當做普通文本,那如果非得要它發生替換呢?(話說怎麼會有這麼二的需求?)辦法就是在這個宏名稱前面加一個#符號,術語叫做:字符串化 ( stringizing )。

注意:這個符號僅僅用於類函數宏中的參數身上,即類對象宏中這種用法不起作用(我試過了,是真的)。

 

##運算符把兩個語言符號組合成單個語言符號,但是它還可用於類對象宏的替換部分,被稱作預處理器的粘合劑。在我看來,它的作用就是給對象(變量或函數)起名時用的,比如我們經常會給變量起x1 x2 x3之類的名字,這類名字的特征就是一部分不變而另一部分變。栗子:

 

#define XNAME(n)   x ## n
然後

 

 

int XNAME (1) = 14;
將被替換成:

 

 

int  x1 = 14:

就醬,全篇完。


 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved