C 巨集高階 e800技術客 赤峰

2021-05-22 14:23:02 字數 3196 閱讀 3525

眾多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巨集高階應用之

在巨集定義中經常看到 和 現在講一下他們的用法 1,是連線符 用於將兩個引數連線在一起 例如 define a x,y x y 則 a he llo 結果是hello define link a my a 則 link god 相當於 mygod 是一種分隔連線方式 他的作用是先分隔 然後強制連線 ...

c 高階一 巨集定義

巨集定義是在編譯前進行的,預處理 1,基本語法 define a 1 令 中的符號a全都替換為1 define use def ifdef use def printf use def endif ifdef 與endif的使用2,常考題 有參巨集定義的使用 define fun a a a int...

C語言巨集的高階應用

關於 和 在c語言的巨集中,的功能是將其後面的巨集引數進行字串化操作 stringfication 簡單說就是在對它所引用的巨集變數通過替換後在其左右各加上乙個雙引號。比如下面 中的巨集 define warn if exp do while 0 那麼實際使用中會出現下面所示的替換過程 warn i...