68

我想知道我们是否可以在 C/C++ 中使用递归宏?如果是,请提供示例。

第二件事:为什么我不能执行下面的代码?我在做什么错误?是因为递归宏吗?

# define pr(n) ((n==1)? 1 : pr(n-1))
void main ()
{
    int a=5;
    cout<<"result: "<< pr(5) <<endl;
    getch();
}
4

5 回答 5

140

宏不会直接递归扩展,但有一些变通方法。当预处理器扫描并展开时pr(5)

pr(5)
^

它创建了一个禁用上下文,因此当它pr再次看到时:

((5==1)? 1 : pr(5-1))
             ^

无论我们尝试什么,它都会变成蓝色,并且无法再扩展。但是我们可以通过使用延迟表达式和一些间接来防止我们的宏变成蓝色:

# define EMPTY(...)
# define DEFER(...) __VA_ARGS__ EMPTY()
# define OBSTRUCT(...) __VA_ARGS__ DEFER(EMPTY)()
# define EXPAND(...) __VA_ARGS__

# define pr_id() pr
# define pr(n) ((n==1)? 1 : DEFER(pr_id)()(n-1))

所以现在它会像这样扩展:

pr(5) // Expands to ((5==1)? 1 : pr_id ()(5 -1))

这是完美的,因为pr从未被漆成蓝色。我们只需要应用另一个扫描以使其进一步扩展:

EXPAND(pr(5)) // Expands to ((5==1)? 1 : ((5 -1==1)? 1 : pr_id ()(5 -1 -1)))

我们可以应用两次扫描以使其进一步扩展:

EXPAND(EXPAND(pr(5))) // Expands to ((5==1)? 1 : ((5 -1==1)? 1 : ((5 -1 -1==1)? 1 : pr_id ()(5 -1 -1 -1))))

但是,由于没有终止条件,我们永远无法应用足够的扫描。我不确定您想要完成什么,但如果您对如何创建递归宏感到好奇,这里有一个如何创建递归重复宏的示例。

首先是应用大量扫描的宏:

#define EVAL(...)  EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL2(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL3(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define EVAL4(...) EVAL5(EVAL5(EVAL5(__VA_ARGS__)))
#define EVAL5(...) __VA_ARGS__

接下来,一个对模式匹配有用的 concat 宏:

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

递增和递减计数器:

#define INC(x) PRIMITIVE_CAT(INC_, x)
#define INC_0 1
#define INC_1 2
#define INC_2 3
#define INC_3 4
#define INC_4 5
#define INC_5 6
#define INC_6 7
#define INC_7 8
#define INC_8 9
#define INC_9 9

#define DEC(x) PRIMITIVE_CAT(DEC_, x)
#define DEC_0 0
#define DEC_1 0
#define DEC_2 1
#define DEC_3 2
#define DEC_4 3
#define DEC_5 4
#define DEC_6 5
#define DEC_7 6
#define DEC_8 7
#define DEC_9 8

一些对条件有用的宏:

#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)

#define NOT(x) CHECK(PRIMITIVE_CAT(NOT_, x))
#define NOT_0 ~, 1,

#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0

#define BOOL(x) COMPL(NOT(x))

#define IIF(c) PRIMITIVE_CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t

#define IF(c) IIF(BOOL(c))

#define EAT(...)
#define EXPAND(...) __VA_ARGS__
#define WHEN(c) IF(c)(EXPAND, EAT)

把它们放在一起,我们可以创建一个重复宏:

#define REPEAT(count, macro, ...) \
    WHEN(count) \
    ( \
        OBSTRUCT(REPEAT_INDIRECT) () \
        ( \
            DEC(count), macro, __VA_ARGS__ \
        ) \
        OBSTRUCT(macro) \
        ( \
            DEC(count), __VA_ARGS__ \
        ) \
    )
#define REPEAT_INDIRECT() REPEAT

//An example of using this macro
#define M(i, _) i
EVAL(REPEAT(8, M, ~)) // 0 1 2 3 4 5 6 7

所以,是的,通过一些解决方法,您可以在 C/C++ 中使用递归宏。

于 2012-09-22T04:20:23.877 回答
24

您的编译器可能提供了仅预处理而不实际编译的选项。如果您试图在宏中查找问题,这很有用。例如使用g++ -E

> g++ -E recursiveMacro.c

# 1 "recursiveMacro.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "recursiveMacro.c"

void main ()
{
    int a=5;
    cout<<"result: "<< ((5==1)? 1 : pr(5 -1)) <<endl;
    getch();
}

如您所见,它不是递归的。pr(x)在预处理过程中只更换一次。要记住的重要一点是,预处理器所做的只是盲目地用另一个文本字符串替换一个文本字符串,它实际上并不评估像(x == 1).

您的代码无法编译的原因是它pr(5 -1)没有被预处理器替换,因此它最终在源代码中作为对未定义函数的调用。

于 2012-09-16T14:20:11.590 回答
19

您不应该在 C 或 C++ 中使用递归宏。

来自 C++ 标准的相关语言,第 16.3.4 节第 2 段:

如果在替换列表的扫描过程中找到被替换的宏的名称(不包括源文件的其余预处理标记),则不会替换它。此外,如果任何嵌套替换遇到被替换的宏的名称,它不会被替换。这些未替换的宏名称预处理标记不再可用于进一步替换,即使它们稍后在该宏名称预处理标记将被替换的上下文中进行(重新)检查。

这种语言有一些回旋的余地。对于多个相互调用的宏,存在一个灰色区域,该措辞并没有完全说明应该做什么。关于这个语言律师问题,有一个针对 C++ 标准的活跃问题;见http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#268

忽略语言律师问题,每个编译器供应商都了解其意图:

C 或 C++ 中不允许使用递归宏。

于 2012-09-16T14:34:26.310 回答
11

很可能您无法执行它,因为您无法编译它。此外,如果它能够正确编译,它总是会返回 1。你的意思是(n==1)? 1 : n * pr(n-1).

宏不能是递归的。根据第 16.3.4.2 章(感谢 Loki Astari),如果在替换列表中找到当前宏,则保持原样,因此您pr的定义不会改变:

如果在替换列表的扫描过程中找到被替换的宏的名称(不包括源文件的其余预处理标记),则不会替换它。此外,如果任何嵌套替换遇到被替换的宏的名称,它不会被替换。这些未替换的宏名称预处理标记不再可用于进一步替换,即使它们稍后在该宏名称预处理标记将被替换的上下文中进行(重新)检查。

你的来电:

cout<<"result: "<< pr(5) <<endl;

由预处理器转换为:

cout<<"result: "<< (5==1)? 1 : pr(5-1) <<endl;

在此期间,pr宏的定义“丢失”,并且编译器显示类似“'pr' is not declared in this scope (fact)”之类的错误,因为没有名为 的函数pr

C++ 不鼓励使用宏。你为什么不写一个函数?

在这种情况下,您甚至可以编写一个模板函数,以便在编译时解析它,并表现为一个常量值:

template <int n>
int pr() {  pr<n-1>(); }

template <>
int pr<1>() { return 1; }
于 2012-09-16T14:27:00.163 回答
-2

您不能在 C 或 C++ 中使用递归宏。

于 2012-09-16T14:18:06.103 回答