C語言宏定義中#與##運算符

#運算符

  宏定義可以包含兩個專用的運算符:#和##。編譯器不會識別這兩個運算符,他們會預處理時被執行。

  #運算符將宏的一個參數轉換爲字符串字面量。它僅允許出現在帶參數的宏的替代列表中。(#運算符所執行的操作可以理解爲“字符串化(stringization)”).

  #運算符有許多用途,這裏只來討論其中的一種。假設我們決定在調試過程中使用PRINT_INT宏作爲一個便捷的方法來輸出整型變量或表達式的值。#運算符可以使PRINT_INT爲每個輸出的值添加標籤。下面是改進後的PRINT_INT:

         #define PRINT_INT(n) printf(#n " = %d\n",n)

n之前的#運算符通知預處理器根據PRINT_INT的參數創建一個字符串字面量。因此,調用

         PRINT_INT(i/j);

會變爲

         printf("i/j" " = %d\n",i/j);

等價爲printf("i/j = %d\n",i/j);

##運算符

##運算符可以將兩個記號(如標示符)"粘合"在一起,成爲一個記號。如果其中一個操作數是宏參數,“粘合”會在形式參數被相應的實際參數替換後發生。考慮下面的宏:

      #define MK_ID(n) i##n

當MK_ID被調用時(MK_ID(1)),預處理器首先使用實際參數替換形式參數n。接着,預處理器將i和1合併成爲一個記號(i1)。下面的聲明使用MK_ID創建了3個標識符:

     int MK_ID(1),MK_ID(2),MK_ID(3);

預處理後這一聲明變爲:int i1,i2,i3;

如果要被“字符串化”的參數包含“或\字符,#運算符會將"轉換爲\",\轉換爲\\。考慮下面的宏:

#define STRINGIZE(x) #x

預處理器會將STRINGIZE("foo")替換爲”\“foo\""。

替換列表中依賴##的宏通常不能嵌套調用。如:

#define CONCAT(x,y) x##y 儘管CONCAT(a,b)會如期望那樣得到ab,但CONCAT(a,CONCAT(b,c))會給出一個怪異的結果。這裏的問題在於CONCAT(a,CONCAT(b,c))不會按照"正常"的方式擴展——CONCAT(b,c)得出bc。然後CONCAT(a,bc)給出abc。在替換列表中,位於##運算符之前和之後的宏參數在替換時不被擴展,因此,CONCAT(a,CONCAT(b,c))擴展aCONCAT(b,c),而不會進一步擴展,因爲沒有名aCONCAT的宏。

有一種辦法可以解決這個問題,但不好看。技巧是再定義一個宏來調用第一個宏:

#define CONCAT2(x,y) CONCAT(x,y)

用CONCAT2(a,CONCAT2(b,c))就會得到abc。

以上來源於C語言程序設計_現代方法(第2版)

但是,如果在替換文本中,參數名以#作爲前綴則結果將被擴展爲由實際參數替換該參數的帶引號的字符串。例如,可以將它與字符串連接運算結合起來編寫一個調試打印宏:#define dprint(expr) printf(#expr " = %g\n", expr)使用語句dprint(x/y)調用該宏時,該宏將被擴展爲:printf("x/y" " = &g\n", x/y);其中的字符串被連接起來了,這樣,該宏調用的效果等價於printf("x/y = &g\n", x/y);在實際參數中,每個雙引號"將被替換爲\",反斜槓\將被替換爲\\,因此替換後的字符串是合法的字符串常量。預處理器運算符##爲宏擴展提供了一種連接實際參數的手段。如果替換文本中的參數與##相鄰,則該參數將被實際參數替換,##與前後的空白符將被刪除,並對替換後的結果重新掃描。例如,下面定義的宏paste用於連接兩個參數#define paste(front, back) front ## back因此,宏調用paste(name, 1)的結果將建立記號name1。

兩個特殊的運算符會影響替換過程。首先,如果替換記號序列中的某個形式參數前面直接是一個#符號(它們之間沒有空白符),相應形式參數的兩邊將被加上雙引號("),隨後,#和形式參數標識符將被用引號引起來的實際參數替換。實際參數中的字符串字面值、字符常量兩邊或內部的每個雙引號(")或反斜槓(\)前面都要插入一個反斜槓(\)。其次,無論哪種宏的定義記號序列中包含一個##運算符,在形式參數替換後都要把##及其前後的空白符都刪除掉,以便將相鄰記號連接起來形成一個新記號。如果這樣產生的記號無效,或者結果依賴於##運算符的處理順序,則結果沒有定義。同時,##也可以不出現在替換記號序列的開頭或結尾。對這兩種類型的宏,都要重複掃描替換記號序列以查找更多的已定義標識符。但是。當某個標識符在某個擴展中被替換後,再次掃描並再次遇到此標識符時不再對其執行替換,而是保持不變。即使執行宏擴展後得到的最終結果以#打頭,也不認爲它是預處理指令。說明:有關宏擴展處理的細節信息,ANSI標準比第1版描述得更詳細。最重要的變化是加入了#和##運算符,這就使得引用和連接成爲可能。某些新規則(特別是與連接有關的規則)比較獨特(參見下面的例子)。

例如,這種功能可用來定義“表示常量”,如下例所示:

#define TABSIZE 100

int table[TABSIZE];

定義

#define ABSDIFF(a, b) ((a)>(b) ? (a)-(b) : (b)-(a))

定義了一個宏,它返回兩個參數之差的絕對值。與執行同樣功能的函數所不同的是,參數與返回值可以是任意算術類型,甚至可以是指針。同時,參數可能有副作用,而且需要計算兩次,一次進行測試,另一次則生成值。假定有下列定義:

#define tempfile(dir) #dir "%s"

宏調用tempfile(/usr/tmp)將生成"/usr/tmp" "%s"隨後,該結果將被連接爲一個單個的字符串。給定下列定義:#define cat(x, y) x ## y那麼,宏調用cat(var, 123)將生成var123。但是,宏調用cat(cat(1,2),3)沒有定義:##阻止了外層調用的參數的擴展。因此,它將生成下列記號串:cat ( 1 , 2 )3並且,)3 (不是一個合法的記號,它由第一個參數的最後一個記號與第二個參數的第一個記號連接而成。如果再引入第二層的宏定義,如下所示:#define xcat(x, y) cat(x,y)我們就可以得到正確的結果。xcat(xcat(1, 2), 3)將生成123,因爲xcat 自身的擴展不包含##運算符。類似地,ABSDIFF(ABSDIFF(a,b),c)將生成所期望的經完全擴展後的結果。

以上來源C程序設計語言(第2版)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章