C/C++ 宏具體解析。本站提示廣大學習愛好者:(C/C++ 宏具體解析)文章只能為提供參考,不一定能成為您想要的結果。以下是C/C++ 宏具體解析正文
浩瀚C++書本都忠言我們C說話宏是萬惡之首,但工作總不如我們想象的那末壞,就好像goto一樣。宏有一個很年夜的感化,就是主動為我們發生代碼。假如說模板可認為我們發生各類型其余代碼(型別調換),那末宏其實可認為我們在符號上發生新的代碼(即符號調換、增長)。
關於宏的一些語法成績,可以在谷歌上找到。信任我,你關於宏的懂得相對沒你想象的那末多。假如你還不曉得#和##,也不曉得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):
#define TTL_TPARAMS(n) TTL_TPARAMSX(n,T)
=> TTL_TPARAMSX( 1, T )
#define TTL_TPARAMSX(n,t) TTL_REPEAT(n, TTL_TPARAM, TTL_TPARAM_END, t)
=> TTL_REPEAT( 1, TTL_TPARAM, TTL_TPARAM_END, T )
#define TTL_TPARAM(n,t) typename t##n,
#define TTL_TPARAM_END(n,t) typename t##n
#define TTL_REPEAT(n, m, l, p) TTL_APPEND(TTL_REPEAT_, TTL_DEC(n))(m,l,p) TTL_APPEND(TTL_LAST_REPEAT_,n)(l,p)
留意,TTL_TPARAM, TTL_TPARAM_END固然也是兩個宏,他們被作為TTL_REPEAT宏的參數,依照prescan規矩,仿佛應當先將這兩個宏睜開再傳給TTL_REPEAT。然則,好像我在後面重點提到的,這兩個宏是function-like macro,應用時須要加括號,假如沒加括號,則欠妥作宏處置。是以,睜開TTL_REPEAT時,應當為:
=> TTL_APPEND( TTL_REPEAT_, TTL_DEC(1))(TTL_TPARAM,TTL_TPARAM_END,T) TTL_APPEND( TTL_LAST_REPEAT_,1)(
TTL_TPARAM_END,T)
這個宏體看起來很龐雜,細心剖析下,可以分為兩部門:
TTL_APPEND( TTL_REPEAT_, TTL_DEC(1))(TTL_TPARAM,TTL_TPARAM_END,T)和
TTL_APPEND( TTL_LAST_REPEAT_,1)(TTL_TPARAM_END,T)
先剖析第一部門:
#define TTL_APPEND( x, y ) TTL_APPEND1(x,y) //先睜開x,y再將x,y銜接起來
#define TTL_APPEND1( x, y ) x ## y
#define TTL_DEC(n) TTL_APPEND(TTL_CNTDEC_, n)
依據先睜開參數的准繩,會先睜開TTL_DEC(1)
=> TTL_APPEND(TTL_CNTDEC_,1) => TTL_CNTDEC_1
#define TTL_CNTDEC_1 0 留意,TTL_CNTDEC_不是宏,TTL_CNTDEC_1是一個宏。
=> 0 , 也就是說,TTL_DEC(1)終究被睜開為0。回到TTL_APPEND部門:
=> TTL_REPEAT_0 (TTL_TPARAM,TTL_TPARAM_END,T)
#define TTL_REPEAT_0(m,l,p)
TTL_REPEAT_0這個宏為空,那末,下面說的第一部門被疏忽,如今只剩下第二部門:
TTL_APPEND( TTL_LAST_REPEAT_,1)(TTL_TPARAM_END,T)
=> TTL_LAST_REPEAT_1 (TTL_TPARAM_END,T) // TTL_APPEND將TTL_LAST_REPEAT_和1歸並起來
#define TTL_LAST_REPEAT_1(m,p) m(1,p)
=> TTL_TPARAM_END( 1, T )
#define TTL_TPARAM_END(n,t) typename t##n
=> typename T1 睜開終了。
固然我們剖析出來了,然則這其實其實不是我們想要的。我們應當從那些宏裡去獲得作者關於宏的編程思惟。很好地應用宏看上去仿佛是一些偏門的奇技淫巧,然則他確切可讓我們編碼更主動化。