我们可以使用递归宏吗?

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

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

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

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

您的编译器可能只提供了一个预处理选项,而没有提供实际的编译选项。如果您试图在宏中查找问题,这非常有用。例如使用 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)没有被预处理器替换,因此它最终会作为对未定义函数的调用出现在源代码中。

最有可能的情况是,您无法执行它,因为您无法编译它。而且,如果它能够正确编译,它将总是返回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宏的定义是‘ lost’,编译器显示一个错误,比如“‘ pr’未在此范围(事实)中声明”,因为没有名为 pr的函数。

C + + 不鼓励使用宏,为什么不写个函数呢?

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

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


template <>
int pr<1>() { return 1; }

在 C 或 C + + 中,不是 假设拥有递归宏。

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

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

这种语言还是有回旋余地的。对于多个相互调用的宏,存在一个灰色地带,其措辞没有明确说明应该做什么。关于这个语言律师问题,有一个反对 C + + 标准的活跃问题; 请参阅 http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#268

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

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

宏不会直接递归地展开,但是有一些变通方法。当预处理器扫描并展开 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))))

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

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

#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)

将所有这些放在一起,我们可以创建一个 repeat 宏:

#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 + + 中使用递归宏。

TLDR. 真正的递归本身很容易实现,只需在两个名称中复制宏,每个名称引用另一个名称。但是这个特性的有用性值得怀疑,因为这需要嵌套的条件宏来使递归有限。所有的条件宏操作符本质上都是多行的,因为 # else 和 # endf 行必须是单独的行(严重的 cpp 限制) ,这意味着,条件宏定义在设计上是不可能的(因此,递归本身是无用的)。