c語言中的巨集是乙個很簡單粗暴的設計,主要功能就是replace。為了更方便地替換,引入了巨集函式這一概念。巨集函式用引數替換預先定義的識別符號在巨集定義中的每一次出現。配合#和##,可以用巨集簡單高效地完成一些複雜的操作。#稱之為字串化操作符(stringizing operator),它將函式巨集的實際引數轉換為對應的字串常量。
舉個例子:
// stringizer.c
#include
#define max 100
#define stringer( x ) printf( #x "\n" )
// 這裡使用了#.
intmain()
in quotes in the printf function call
in quotes in the printf function call
"in quotes when printed to the screen"
"this: \" prints an escaped double quote"
max
從上面的例子我們可以看到,#的功能比較容易理解:其實就是將原本巨集應該展開的內容用"「括起來形成乙個字面值常量。那是不是相當於把巨集函式的引數內容兩邊直接加個」"呢?不,不只是這樣。#對空格以及特殊字元的處理正是理解使用#時的難點。事實上,上面的例子最後展開的結果是:
#include
#define max 100
#define stringer( x ) printf( #x "\n" )
// 這裡使用了#.
intmain()
注意到了嗎?第一行和第二行的結果是一致的,中間的空格被忽略了一部分!操作符對空格的處理是:忽略傳入引數名前面和後面的空格;當傳入引數名間存在空格時,編譯器將會自動連線各個子字串,用每個子字串中只以乙個空格連線,忽略其中多於乙個的空格。
第三行,第四行中,會把字串中的特殊字元前會自動新增\轉義字元
,保證得到的字串常量是引數本身的樣子。當引數是"\m"時,轉換後的結果不是""\m"" 而是\"\\\m\"。 而c語言編譯時,\"\\\m\" 正對應著記憶體中的"\m"。在第5行中預先定義的巨集max並沒有展開,替換為字串時並不進行巨集展開這一點一定要注意
。這個可以解釋為預處理器並不替換字串中的巨集(被引號包起來的巨集)。如果想讓巨集展開應該怎麼做呢?這個問題我們稍後再講。
總結一下,對於#操作符來說
」#「的功能是對引數執行字串化,如果沒有特殊情況,這就意味著直接用""將引數包裹起來做替換。
如果實際引數中包含在字串中使用時需要轉義的字元(比如"和),那麼這些字元就會被轉義。
被字串化的文字中的所有前導和尾隨空白被忽略。文字中間的任何空格序列都將轉換為單個空格。至於注釋,因為注釋往往在編譯器處理源**剛開始就被去除,遠早於字串化的發生,所以注釋不可能包含在轉換的結果中。
引數直接轉換成字串,引數中的巨集不展開。
##稱之為標記貼上運算子(token-pasting operator),也可以叫做合併運算子(「merging」 operator)。簡單的說,「##」是一種分隔連線方式,它的作用是先分隔,然後進行強制連線。
當巨集展開時,位於##兩邊的標記合併成乙個識別符號,如果##兩邊的識別符號時巨集函式的引數時,用實際引數取代識別符號後再合併。##兩邊的空格在合併時都會被刪除,空格多少是無關緊要的。
#define a1(name, type) type name_ ## type ## _type
#define a2(name, type) type name ## _ ## type ## _type
a1(a1, int); /* 等價於: int name_int_type; */
a2(a1, int); /* 等價於: int a1_int_type; */
##可以用在巨集函式以外的地方,但一般來說,用在巨集函式之外的地方並無多大意義。##真正發光發熱的時候是用在巨集函式時,用引數替換形成了新的識別符號。比如#define my_macro(x) x##_macrofied這個巨集在使用時my_macro(identifier)展開為identifier_macrofied。
如果合併後的結果不是有效的識別符號,如mai ## n ##()合併後產生main(), 這時編譯器如何處理是未定義的。目前gcc是不允許這種情況的,會報如pasting 「main」 and 「(」 does not give a valid preprocessing token這樣的錯誤。而visual c++是允許這種行為的。這裡的識別符號實際上不止是變數名的意思,如#define macro_increment(x) x+ ## +這樣的巨集gcc和visual c++都是允許的,因為巨集展開後得到x++,這可以解釋為x和++兩個識別符號。
一般而言,並不特別鼓勵使用c/c++的巨集,因為巨集是簡單的字串替換,特別容易出錯,還不利於ide的補全等等功能。因此使用巨集就需要特別注意,沒有很好的理由一般不使用巨集,對於#,##更是如此。
在介紹##和#的應用場景之前,先回答乙個問題:如何展開引數中的巨集?
使用#替換字串時不進行巨集展開
,為了使引數中的巨集展開後再轉成字串常量,需要兩個巨集:
#define xstr(s) str(s)
#define str(s) #s
#define foo 4
這時候使用str (foo)展開為"foo",如果用xstr (foo),會優先展開foo成為4,按xstr (4)→str (4)→"4"的順序逐步展開
。
這樣做的原因是除非是遇到#或者##,否則巨集函式的引數一定是完全展開後再做巨集函式引數,關於巨集展開順序更詳細的解釋可以參考gcc的文件。
巨集最常見的使用場景就是減少重複,以減少犯錯,看下面這個例子:
在某些程式命令表中需要命令表,一般情況下是這樣的申明方式:
struct command
;// command table.
struct command commands=
,,…}
;
這樣的場景下,更好的方式是定義乙個巨集,程式變成了這樣:
#define command(name)
struct command commands=
;
如果程式中有一系列函式或者結構體有相同的結構,但為了效率不能整合在一起等等其它因素必須寫相似的**很多遍,可以嘗試用##和#實現,比如需要寫一系列convert函式時,可以用下面這個巨集
/*
from – a descriptive name of the unit we are converting from
to – a descriptive name of the unit we are converting to
conversion – the conversion equation
(yes, macro parameters can be complex)
from_type – the type we are converting from
to_type – the type we are converting to
*/#define convert(from, to, conversion, from_type, to_type) \
to_type convert_##from##_to_##to(from_type f) \
\
這樣就可以用其宣告一些類似的函式:
convert
(f, c,
(f-32)*
5.0/
9.0,
float
,float);
convert
(ft, in, ft *12,
int,
int)
;
assert和log場景下,經常需要將部分**原樣輸出,這時候#的應用就很廣泛了,舉幾個最簡單的例子
#define assert(x) ((x)?(void)0:__assert(#x, __file__, __line__))
#define showlist(...) puts(#__va_args__)
showlist()
;// expands to puts("")
showlist(1
,"x"
,int);
// expands to puts("1, \"x\", int")
巨集因為其只是簡單地字串替換,使用時顧忌很多,尤其是在c++中,除了條件編譯甚至可以說是建議盡量不使用巨集了。然而#,和##的妥善使用在精簡重複**,提示可讀性時有很多應用,是c/c++預處理機制中非常關鍵的部分,用好了可以使**可讀性有很大提高,同時踐行dry法則。 C巨集的用法
前幾天參加某公司的筆試,有一道題是用純c實現乙個泛型函式。鬱悶了好久用c 模板實現了。巨集有如下的特點 1.與const相比,巨集是在預編譯的時候完成的 2.define 只做簡單的替換,不做型別安全檢查 3.使用不當會引起很多問題 巨集的用法 1.簡單的巨集定義 define max 1024 2...
c語言 巨集中 的用法(續)
如下 不明覺厲。define ext2 debug f,a.特別是其中printf f,a 這樣的用法。google之後在stackoverflow找到了這個問題以及解答。詳細的解答跳轉到了gnu的gcc文件 譯文如下 3.6 variadic macros 可變引數的巨集 乙個巨集可以像乙個函式一...
C語言巨集中的 和 的用法
與 在巨集定義中的 巨集展開 include define f a,b a b define g a a define h a g a int main 巨集展開時 如果巨集定義以 開頭,不展開引數,直接替換。故g f 1,2 f 1,2 f 1,2 如果巨集定義不以 開頭,展開引數,直接替換,由外...