【问题标题】:On using recursion in C在 C 中使用递归
【发布时间】:2014-01-04 11:11:30
【问题描述】:

在用 C 实现词法分析器时,我发现自己正在编写递归代码:

// Return a List of Tokens
List Lexer_run(const char* input) {
    if(input[0] == '\0') {
        return new_List();
    } else {
        // ... Find Token and possibly advance in input for a few characters
        return List_add(Lexer_run(nextInput), newToken);
    }

考虑另一个链表实现的例子

List_length(List* this) {
    if(!this) {
        return 0;
    } else {
        return 1 + List_length(this->next);
    }
}

我想知道我是否总是可以在 C 中使用这样的递归代码,或者我是否应该避免它,除非是这种情况 确实需要递归(例如递归下降解析器或树结构)

我目前的想法

递归的优点:

  • 可读且优雅

缺点:

  • 会迅速导致堆栈溢出(在我的计算机中大约有 1'000'000 次调用)
  • 与迭代版本相比可能效率低下

解决方案:

  • 使用尾调用优化并让编译器将我的递归转换为循环,但我发现尾调用代码的可读性较差。
  • 为我的程序增加堆栈大小

注意

我的问题不是专门针对我的示例,而是一个一般性问题,是否应该使用 C中的递归。

【问题讨论】:

  • 要回答这篇文章中唯一的问题是:不,避免它,除非它带来了重要除了更少的击键。
  • “我想知道我是否总是可以在 C 中使用这样的递归代码” - 你总是可以使用递归,因为 C 允许任何函数调用自己(甚至是 main())。如果递归代码比迭代代码更易读,我们也鼓励您编写递归代码。是的,递归可能会更慢。是的,它会导致堆栈溢出。这并不总是很重要——不要过早优化。
  • @WhozCraig 我不同意。这不是关于“更少的击键”。递归可以更容易理解(如果我不在一个每秒计算数十亿个浮点数的紧密循环中,我并不真正关心如果代码更具可读性和递归丢失的“效率”更容易掌握。)
  • 没有什么规定递归函数必须对所有任务使用递归——例如函数解析xml 数据甚至递归下降解析器可以而且应该在适当的情况下使用迭代和递归控制机制。
  • @H2CO3 你还会使用尾递归还是普通的?

标签: c recursion


【解决方案1】:

通常,您希望将递归用于本质上是递归的任务,例如遍历递归数据结构、序列化递归定义的结构或从“平面”输入生成递归结构(例如解析语言)。您不希望对可以用迭代表示的任务应用递归,例如行走线性数据结构。

递归是一种强大的机制,但使用它代替迭代就像用大锤拍苍蝇 *。内存效率和堆栈溢出的可能性都是非常重要的考虑因素,但它们对于代码的可理解性是次要的。即使您通过让编译器为您优化尾调用来消除在迭代就足够的情况下应用递归的负面后果,您的程序的读者也会摸不着头脑,试图首先了解您做了什么,然后再了解您为什么这样做。

当您将递归应用于递归任务(树、递归下降解析器、处理整个输入的分而治之算法、回溯搜索)时,您的代码会变得更具可读性,因为它与手头的任务相匹配。另一方面,当您将递归应用于固有的非递归任务时,您的代码会变得更难阅读。

* 这个关于递归与迭代的比喻是从 Dijkstra 的一本书的介绍性章节中借用的。

【讨论】:

    【解决方案2】:

    坦率地说,尾调用优化是不可信的。有太多的陷阱会吓跑优化器,使其无法在看似无害的情况下应用它。很高兴它在那里,但不要依赖它。

    因此(并且由于堆栈大小固定),您通常希望避免递归,除非您确实需要它的隐式堆栈结构 [您不需要,因为您的 List_length]。即使这样,也要注意堆栈溢出的可能性。

    编写递归函数的主要好处是可读性。这使它们成为诸如递归下降之类的算法的“初稿”材料,这些算法具有自然的递归结构。然后在遇到麻烦时将它们重写为迭代(必要时使用堆栈)。

    这样做的另一个好处是:您可以保留递归版本作为参考实现,并对它们进行单元测试以确保它们是等效的。

    【讨论】:

    • +1 表示将其作为测试参考。我认为这并不像应有的那么明显。
    猜你喜欢
    • 2012-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-18
    • 2017-10-03
    • 2020-08-10
    • 2016-08-07
    • 2017-08-16
    相关资源
    最近更新 更多