首頁  >  文章  >  後端開發  >  我們如何在 C 中實作遞歸巨集?

我們如何在 C 中實作遞歸巨集?

Barbara Streisand
Barbara Streisand原創
2024-11-17 19:49:02139瀏覽

How Can We Implement Recursive Macros in C?

了解巨集參數的巨集遞歸

在 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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn