了解巨集參數的巨集遞歸
在 C 程式設計中,巨集為文字運算提供了強大的工具。一個有趣的方面是能夠在其他巨集的參數上使用巨集。然而,這提出了技術挑戰,因為語言通常不允許遞歸巨集。
問題:遞歸宏
考慮我們希望建立的場景名為 PRINT_ALL 的 foreach 宏,它將給定的宏 PRINT 套用於參數清單。例如:
int a = 1, b = 3, d = 0; PRINT_ALL(a,b,d);
這將對每個變數 a、b 和 d 呼叫 PRINT 巨集。簡單的方法可能會使用遞歸宏,如下所示:
#define FIRST_ARG(arg,...) arg #define AFTER_FIRST_ARG(arg,...) , ##__VA_ARGS__ #define PRINT(a) printf(#a": %d", a) #define PRINT_ALL PRINT(FIRST_ARG(__VA_ARGS__)); PRINT_ALL(AFTER_FIRST_ARG(__VA_ARGS__))
但是,這種方法會帶來兩個問題:宏不能遞歸地呼叫自身,並且它缺乏停止遞歸的停止條件。
遞歸解決方法
克服這些問題為了克服這些障礙,一個聰明的解決方法是利用一種稱為宏評估遞歸的技術。關鍵思想是發出模擬巨集呼叫的巨集文本,而不實際呼叫巨集本身。
考慮以下巨集:
#define MAP_OUT
如果我們有下列巨集:
#define A(x) x B MAP_OUT (x) #define B(x) x A MAP_OUT (x)
計算宏A(blah) 會產生輸出文字:
blah B (blah)
此文字用作巨集替換佔位符。它可以被傳回預處理器以進一步擴展,繼續巨集評估過程。
為了促進這種遞歸,定義了一系列 EVAL 巨集:
#define EVAL0(...) __VA_ARGS__ #define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__))) #define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__))) #define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__))) #define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__))) #define EVAL(...)EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
每個巨集都適用多個層級的評估,從而放大所應用的巨集的效果。
停止遞歸
為了控制遞歸,定義了一個特殊的宏MAP_END:
#define MAP_END(...)
評估此宏不執行任何操作,有效地終止遞歸。
下一個挑戰是確定何時使用 MAP_END 而不是繼續遞歸。為了實現這一點,MAP_NEXT 巨集將清單項目與特殊的清單結束標記進行比較。如果匹配,則返回 MAP_END;否則,返回下一個參數:
#define MAP_GET_END() 0, MAP_END #define MAP_NEXT0(item, next, ...) next MAP_OUT #define MAP_NEXT1(item, next) MAP_NEXT0(item, next,0) #define MAP_NEXT(item, next) MAP_NEXT1(MAP_GET_END item, next)
透過仔細建構 MAP_NEXT 宏,我們可以控制遞歸是繼續還是結束。
最終實現
結合這些構建塊,我們可以創建MAP 宏,它迭代列表並將給定的宏應用於每個item:
#define MAP(f,...)EVAL(MAP1(f,__VA_ARGS__,(),0))
該巨集的工作原理是在列表末尾放置一個列表結束標記,並附加一個額外參數以確保符合ANSI 要求。然後它透過多個 EVAL 巨集呼叫傳遞列表並傳回結果。
此技術為在巨集參數上使用巨集的問題提供了創造性的解決方案。它支援複雜的巨集操作功能,允許程式設計師以新穎的方式擴展預處理器的功能。
以上是我們如何在 C 中實作遞歸巨集?的詳細內容。更多資訊請關注PHP中文網其他相關文章!