C C 巨集講解

2021-09-14 05:03:22 字數 3196 閱讀 2398

眾多c++書籍都忠告我們c語言巨集是萬惡之首,但事情總不如我們想象的那麼壞,就如同goto一樣。巨集有

乙個很大的作用,就是自動為我們產生**。如果說模板可以為我們產生各種型別的**(型別替換),

那麼巨集其實可以為我們在符號上產生新的**(即符號替換、增加)。

關於巨集的一些語法問題,可以在google上找到。相信我,你對於巨集的了解絕對沒你想象的那麼多。如果你

還不知道#和##,也不知道prescan,那麼你肯定對巨集的了解不夠。

我稍微講解下巨集的一些語法問題(說語法問題似乎不妥,macro只與preprocessor有關,跟語義分析又無關):

1. 巨集可以像函式一樣被定義,例如:

#define min(x,y) (x 但是在實際使用時,只有當寫上min(),必須加括號,min才會被作為巨集展開,否則不做任何處理。

2. 如果巨集需要引數,你可以不傳,編譯器會給你警告(巨集引數不夠),但是這會導致錯誤。如c++書籍中所描

述的,編譯器(預處理器)對巨集的語法檢查不夠,所以更多的檢查性工作得你自己來做。

3. 很多程式設計師不知道的#和##

#符號把乙個符號直接轉換為字串,例如:

#define string(x) #x

const char *str = string( test_string ); str的內容就是"test_string",也就是說#會把其後的符號

直接加上雙引號。

##符號會連線兩個符號,從而產生新的符號(詞法層次),例如:

#define sign( x ) int_##x

int sign( 1 ); 巨集被展開後將成為:int int_1;

4. 變參巨集,這個比較酷,它使得你可以定義類似的巨集:

#define log( format, ... ) printf( format, __va_args__ )

log( "%s %d", str, count );

__va_args__是系統預定義巨集,被自動替換為引數列表。

5. 當乙個巨集自己呼叫自己時,會發生什麼?例如:

#define test( x ) ( x + test( x ) )

test( 1 ); 會發生什麼?為了防止無限制遞迴展開,語法規定,當乙個巨集遇到自己時,就停止展開,也就是

說,當對test( 1 )進行展開時,展開過程中又發現了乙個test,那麼就將這個test當作一般的符號。test(1)

最終被展開為:1 + test( 1) 。

6. 巨集引數的prescan,

當乙個巨集引數被放進巨集體時,這個巨集引數會首先被全部展開(有例外,見下文)。當展開後的巨集引數被放進巨集體時,

預處理器對新展開的巨集體進行第二次掃瞄,並繼續展開。例如:

#define param( x ) x

#define addparam( x ) int_##x

param( addparam( 1 ) );

因為addparam( 1 ) 是作為param的巨集引數,所以先將addparam( 1 )展開為int_1,然後再將int_1放進param。

例外情況是,如果param巨集裡對巨集引數使用了#或##,那麼巨集引數不會被展開:

#define param( x ) #x

#define addparam( x ) int_##x

param( addparam( 1 ) ); 將被展開為"addparam( 1 )"。

使用這麼乙個規則,可以建立乙個很有趣的技術:列印出乙個巨集被展開後的樣子,這樣可以方便你分析**:

#define to_string( x ) to_string1( x )

#define to_string1( x ) #x

to_string首先會將x全部展開(如果x也是乙個巨集的話),然後再傳給to_string1轉換為字串,現在你可以這樣:

const char *str = to_string( param( addparam( 1 ) ) );去一探param展開後的樣子。

7. 乙個很重要的補充:就像我在第一點說的那樣,如果乙個像函式的巨集在使用時沒有出現括號,那麼預處理器只是

將這個巨集作為一般的符號處理(那就是不處理)。

我們來見識一下巨集是如何幫助我們自動產生**的。如我所說,巨集是在符號層次產生**。我在分析boost.function

模組時,因為它使用了大量的巨集(巨集巢狀,再巢狀),導致我壓根沒看明白**。後來發現了乙個小型的模板庫ttl,說的

是開發一些小型元件去取代部分boost(這是乙個好理由,因為boost確實太大)。同樣,這個庫也包含了乙個function庫。

這裡的function也就是我之前提到的functor。ttl.function庫里為了自動產生很多類似的**,使用了乙個巨集:

#define ttl_func_build_functor_caller(n) /

template< typename r, ttl_tparams(n) > /

struct functor_caller_base##n /

///...

該巨集的最終目的是:通過類似於ttl_func_build_functor_caller(1)的呼叫方式,自動產生很多functor_caller_base模板:

template struct functor_caller_base1

template struct functor_caller_base2

template struct functor_caller_base3

///...

那麼,核心部分在於ttl_tparams(n)這個巨集,可以看出這個巨集最終產生的是:

typename t1

typename t1, typename t2

typename t1, typename t2, typename t3

///...

我們不妨分析ttl_tparams(n)的整個過程。分析巨集主要把握我以上提到的一些要點即可。以下過程我建議你翻著ttl的**,

相關**檔案:function.hpp, macro_params.hpp, macro_repeat.hpp, macro_misc.hpp, macro_counter.hpp。

so, here we go

分析過程,逐層分析,逐層展開,例如ttl_tparams(1):

雖然我們分析出來了,但是這其實並不是我們想要的。我們應該從那些巨集裡去獲取作者關於巨集的程式設計思想。很好地使用巨集

看上去似乎是一些偏門的奇技淫巧,但是他確實可以讓我們編碼更自動化。

C,C 巨集中 與 的講解

文中 file 與示例1的可以參見 使用ansi c and microsoft c 中常用的預定義巨集 巨集中的 的功能是將其後面的巨集引數進行字串化操作 stringizing operator 簡單說就是在它引用的巨集變數的左右各加上乙個雙引號。如定義好 define string x x之後...

C,C 巨集中 與 的講解

文中 file 與示例1可以參見 使用 ansi c and microsoft c 中常用的預定義巨集 巨集中的 的功能是將其後面的巨集引數進行字串化操作 stringizing operator 簡單說就是在它引用的巨集變數的左右各加上乙個雙引號。如定義好 define string x x之後...

C,C 巨集中 與 的講解

文中 file 與示例1的可以參見 使用ansi c and microsoft c 中常用的預定義巨集 巨集中的 的功能是將其後面的巨集引數進行字串化操作 stringizing operator 簡單說就是在它引用的巨集變數的左右各加上乙個雙引號。如定義好 define string x x之後...