C語言巨集定義##連線符和#符的使用
#include<stdio.h>
#define f(a,b) a##b
#define g(a) #a
#define h(a) g(a)
int main()
{
printf("%s\n",h(f(1,2)));
printf("%s\n",g(f(1,2)));
return 0;
}
在巨集定義裡,a##b就是把a,b聯接起來,
比如f(1,2)就是12,但是是數。
#a就是把a轉化成字串,併合並。
所以 printf("%s\n",g(f(1,2)));就直接把f(1,2)轉成字串了。
#define A(x) T_##x
#define B(x) #@x
#define C(x) #x
我們假設:x=1,則有:
A(1)------〉T_1
B(1)------〉'1'
C(1)------〉"1"
C語言中如何使用巨集C(和C++)中的巨集(Macro)屬於編譯器預處理的範疇,屬於編譯期概念(而非執行期概念)。下面對常遇到的巨集的使用問題做了簡單總結。
關於#和##
在C語言的巨集中,#的功能是將其後面的巨集引數進行字串化操作(Stringfication),簡單說就是在對它所引用的巨集變數 通過替換後在其左右各加上一個雙引號。比如下面程式碼中的巨集:
#define WARN_IF(EXP) do{ if (EXP) fprintf(stderr, "Warning: " #EXP "/n"); } while(0)
那麼實際使用中會出現下面所示的替換過程:
WARN_IF (divider == 0); 被替換為 do { if (divider == 0) fprintf(stderr, "Warning" "divider == 0" "/n"); } while(0);
這樣每次divider(除數)為0的時候便會在標準錯誤流上輸出一個提示資訊。
而##被稱為連線符(concatenator),用來將兩個Token連線為一個Token。注意這裡連線的物件是Token就行,而不一定 是巨集的變數。比如你要做一個選單項命令名和函式指標組成的結構體的陣列,並且希望在函式名和選單項命令名之間有直觀的、名字上的關係。那麼下面的程式碼就非常實用:
struct command { char * name; void (*function) (void); }; #define COMMAND(NAME) { NAME, NAME ## _command } // 然後你就用一些預先定義好的命令來方便的初始化一個command結構的陣列了: struct command commands[] = { COMMAND(quit), COMMAND(help), ... }
COMMAND巨集在這裡充當一個程式碼生成器的作用,這樣可以在一定程度上減少程式碼密度,間接地也可以減少不留心所造成的錯誤。我們還可以n個##符號連線 n+1個Token,這個特性也是#符號所不具備的。比如:
#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d
typedef struct _record_type LINK_MULTIPLE(name,company,position,salary);
// 這裡這個語句將展開為:
// typedef struct _record_type name_company_position_salary;
## 連線符號由兩個井號組成,其功能是在帶引數的巨集定義中將兩個子串(token)聯接起來,從而形成一個新的子串。但它不可以是第一個或者最後一個子串。所謂的子串(token)就是指編譯器能夠識別的最小語法單元。
#符是把傳遞過來的引數當成字串進行替代。
下面來看看它們是怎樣工作的。這是MSDN上的一個例子。 假設程式中已經定義了這樣一個帶引數的巨集:
#define paster( n ) printf( "token" #n " = %d", token##n )
同時又定義了一個整形變數: int token9 = 9;
現在在主程式中以下面的方式呼叫這個巨集: paster( 9 );
那麼在編譯時,上面的這句話被擴充套件為: printf( "token" "9" " = %d", token9 );
注意到在這個例子中,paster(9);中的這個”9”被原封不動的當成了一個字串,與”token”連線在了一起,從而成為了token9。而#n也被”9”所替代。 可想而知,上面程式執行的結果就是在螢幕上列印出token9=9
定義單行巨集:主要有以下三種用法.
1) 前加##或後加##,將標記作為一個合法的識別符號的一部分.注意,不是字串.多用於多行的巨集定義中.例如:
2) 前加#@,將標記轉換為相應的字元,注意:僅對單一標記轉換有效
3) 前加#,將標記轉換為字串.
關於...的使用
...在C巨集中稱為Variadic Macro,也就是變參巨集。比如:
#define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__) // 或者 #define myprintf(templt,args...) fprintf(stderr,templt,args)
第一個巨集中由於沒有對變參起名,我們用預設的巨集__VA_ARGS__來替代它。第二個巨集 中,我們顯式地命名變參為args,那麼我們在巨集定義中就可以用args來代指變參了。同C語言的stdcall一樣,變參必須作為參數列的最有一項出 現。當上面的巨集中我們只能提供第一個引數templt時,C標準要求我們必須寫成:
myprintf(templt,);
的形式。這時的替換過程為:
myprintf("Error!/n",); 替換為: fprintf(stderr,"Error!/n",);
這是一個語法錯誤,不能正常編譯。這個問題一般有兩個解決方法。首先,GNU CPP提供的解決方法允許上面的巨集呼叫寫成:
myprintf(templt);
而它將會被通過替換變成:
fprintf(stderr,"Error!/n",);
很明顯,這裡仍然會產生編譯錯誤(非本例的某些情況下不會產生編譯錯誤)。除了這種方式外,c99和GNU CPP都支援下面的巨集定義方式:
#define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__)
這時,##這個連線符號充當的作用就是當__VAR_ARGS__為空的時候,消除前面的那個逗號。那麼此時的翻譯過程如下:
myprintf(templt); 被轉化為: fprintf(stderr,templt);
這樣如果templt合法,將不會產生編譯錯誤。 這裡列出了一些巨集使用中容易出錯的地方,以及合適的使用方式。
錯誤的巢狀-Misnesting
巨集的定義不一定要有完整的、配對的括號,但是為了避免出錯並且提高可讀性,最好避免這樣使用。
由操作符優先順序引起的問題-Operator Precedence Problem
由於巨集只是簡單的替換,巨集的引數如果是複合結構,那麼通過替換之後可能由於各個引數之間的操作符優先順序高於單個引數內部各部分之間相互作用的操作符優先順序,如果我們不用括號保護各個巨集引數,可能會產生預想不到的情形。比如:
#define ceil_div(x, y) (x + y - 1) / y
那麼
a = ceil_div( b & c, sizeof(int) );
將被轉化為:
a = ( b & c + sizeof(int) - 1) / sizeof(int); // 由於+/-的優先順序高於&的優先順序,那麼上面式子等同於: a = ( b & (c + sizeof(int) - 1)) / sizeof(int);
這顯然不是呼叫者的初衷。為了避免這種情況發生,應當多寫幾個括號:
#define ceil_div(x, y) (((x) + (y) - 1) / (y))
消除多餘的分號-Semicolon Swallowing
通常情況下,為了使函式模樣的巨集在表面上看起來像一個通常的C語言呼叫一樣,通常情況下我們在巨集的後面加上一個分號,比如下面的帶參巨集:
MY_MACRO(x);
但是如果是下面的情況:
#define MY_MACRO(x) { /* line 1 */ /* line 2 */ /* line 3 */ } //... if (condition()) MY_MACRO(a); else {...}
這樣會由於多出的那個分號產生編譯錯誤。為了避免這種情況出現同時保持MY_MACRO(x);的這種寫法,我們需要把巨集定義為這種形式:
#define MY_MACRO(x) do { /* line 1 */ /* line 2 */ /* line 3 */ } while(0)
這樣只要保證總是使用分號,就不會有任何問題。
Duplication of Side Effects
這裡的Side Effect是指巨集在展開的時候對其引數可能進行多次Evaluation(也就是取值),但是如果這個巨集引數是一個函式,那麼就有可能被呼叫多次從而達到不一致的結果,甚至會發生更嚴重的錯誤。比如:
#define min(X,Y) ((X) > (Y) ? (Y) : (X)) //... c = min(a,foo(b));
這時foo()函式就被呼叫了兩次。為了解決這個潛在的問題,我們應當這樣寫min(X,Y)這個巨集:
#define min(X,Y) ({ typeof (X) x_ = (X); typeof (Y) y_ = (Y); (x_ < y_) ? x_ : y_; })
({...})的作用是將內部的幾條語句中最後一條的值返回,它也允許在內部宣告變數(因為它通過大括號組成了一個區域性Scope)。
相關文章
- C語言巨集定義中#define中的井號#的使用C語言
- 【C】 13_接續符和轉義符
- c語言巨集的使用C語言
- Solidity語言學習筆記————19、函式可見性定義符、修飾符、保留字和語法Solid筆記函式
- C語言-識別符號命名C語言符號
- c 語言中巨集定義和定義全域性變數的區別變數
- C語言巨集中"#"和"##"的用法C語言
- 1413: C語言合法識別符號C語言符號
- Go 語言指標符號 *和&Go指標符號
- C語言零基礎教程之預處理和巨集定義篇C語言
- 命令注入-命令的連線符【‘&’‘&&’‘||’‘|’】的含義及其用法
- C語言學習第18篇---巨集定義與使用 / 條件編譯使用分析C語言編譯
- C語言合法識別符號 hd 2024C語言符號
- SCSS 字串連線符CSS字串
- 資料庫中字串連線符的使用資料庫字串
- 連結使用的符號符號
- 符號(註釋符+轉義符+接續符)符號
- c語言的定義與宣告C語言
- <Python>識別符號、變數的定義與使用Python符號變數
- C語言巨集和函式淺析C語言函式
- C語言中的標頭檔案中的巨集定義C語言
- [C]有符號數和無符號數符號
- Python 賦值與運算子和連線符Python賦值
- 微控制器-C語言-定義和申明C語言
- 【C進階】21、巨集定義與使用分析
- C語言基礎-2、函式的定義與使用C語言函式
- c語言中const修飾符C語言
- 如何在VirtualBox客戶機使用符號連線符號
- flutter【4】dart語言--操作符FlutterDart
- c語言函式指標的定義C語言函式指標
- C++中巨集定義#define的用法C++
- 解析C++連結錯誤:未定義引用和未解析符號的完整解決方案C++符號
- C語言線性連結串列C語言
- 幽默:交通標誌是一種符號和交通語言符號
- 巨集定義
- 人工智慧各學派簡介:符號主義,連線主義,行為主義人工智慧符號
- 符號連結符號
- C語言中水平製表符 與退格鍵 的使用方法探索C語言
- C++ | VS2017 C++專案配置使用的常見巨集定義C++