【问题标题】:How to handle recursion in member functions?如何处理成员函数中的递归?
【发布时间】:2020-09-07 07:53:00
【问题描述】:

例如,我有一个 empty 函数来清除链表:

void empty(Node* head) {
        if (head->next) { empty(head->next); }
        delete head;
        head = nullptr;
    }

但是后来我为链表创建了一个类,所以现在我不需要传递head 参数:

void empty() {
        if (head->next) { empty(head->next); }
        delete head;
        head = nullptr;
    }

但是empty(head->next) 行显然是错误的,因为empty 不接受任何参数。我想到了在函数内部创建一个函数(使用 lambda)的想法,如下所示:

void empty() {
        std::function<void(Node*)> emptyWrapper = [&] (Node* l_head) {
            if (l_head->next) { emptyWrapper(l_head->next); }
            delete l_head;
            l_head = nullptr;
        };
        emptyWrapper(head);
    }

但我想知道是否有更好的方法来做到这一点。 Lambdas 最近成为了我的一种想法。

【问题讨论】:

  • "但是后来我为链表创建了一个类,所以现在我不需要传递 head 参数:" 递归函数的 head 和你的类成员不是一回事。他们只共享名字。
  • @CaptainGiraffe Node* head; empty(head);MyClass head; head.empty(); 之间没有区别。
  • 创建一个私有辅助函数,使empty()成为公共世界的接口。
  • 如果你坚持使用递归,那么让你的公开函数empty成为一个包含参数的递归函数的包装器。例如void empty() { empty(head); } … void empty( Node *node ) {…}
  • while ( head ) { Node *t = head-&gt;next; delete head; head = t; } ...实际上你已经在做,但实际上要好一点...如果head已经是nullptr,你的版本将有UB

标签: c++ class recursion singly-linked-list function-definition


【解决方案1】:

一般的方法是声明一个公共成员函数,然后调用一个私有静态递归成员函数。

注意empty 这个名字听起来很混乱。最好将函数命名为clear

你来了

#include <functional>

//...

class List
{
public:
    //...
    void clear() 
    {
        clear( head );
    }

private:
    static void clear( Node * &head )
    {
        if ( head )
        {
            delete std::exchange( head, head->next );
            clear( head ); 
        }
    }
    //...
}

无需定义辅助静态函数即可使用相同的方法。

void clear()
{
    if ( head )
    {
        delete std::exchange( head, head->next );
        clear();
    }
}

这是一个演示程序。

#include <iostream>
#include <iomanip>
#include <functional>

template <typename T>
class List
{
private:
    struct Node
    {
        T data;
        Node *next;
    } *head = nullptr;

public:
    void push_front( const T &data )
    {
        head = new Node { data, head };
    }

    friend std::ostream & operator <<( std::ostream &os, const List &list )
    {
        for ( Node *current = list.head; current; current = current->next )
        {
            os << current->data << " -> ";
        }

        return os << "null";
    }

    bool empty() const { return head== nullptr; }

    void clear()
    {
        if ( head )
        {
            delete std::exchange( head, head->next );
            clear();
        }
    }
};

int main() 
{
    List<int> list;

    const int N = 10;

    for ( int i = N; i != 0; )
    {
        list.push_front( i-- );
    }

    std::cout << list << '\n';

    list.clear();

    std::cout << "list is empty " << std::boolalpha << list.empty() << '\n';

    return 0;
}

程序输出是

1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> null
list is empty true

【讨论】:

  • delete std::exchange( head, head-&gt;next ); 你打败了我。不错!
【解决方案2】:

注意:正如其他人所提到的,您不需要在这里使用递归。此示例假设您出于某种未提及的原因想要或需要。这就是您使用递归的方式。但是,从长远来看,使用循环进行重组可能是您应该做的。


您可以制作列表的公开和私有版本:

class list {
public:
void empty();

//...
private:
void empty(Node* head);
// alternatively, you could make this static instead:
static void empty(Node* head);

//...

}

然后你可以调用empty(),它从另一个empty()内部获取一个参数:

void list::empty() {
    if(this->head) {  // check here so ->next won't fail in the helper function.
                      // maybe you should add a check there instead
        empty(this->head);
    }
}

附:您可能不应该像我在这里所做的那样到处使用名称head。这只是我拼凑的一个简单示例。但至少您可以通过这种方式了解总体思路。

【讨论】:

    【解决方案3】:

    直接的解决方案是使用一个辅助函数来完成递归函数所做的工作。

    class List{
    public:
       void empty(){
            if (head) { empty_helper(head); }
            delete head;
            head = nullptr;
        }
    private:
    // should probably be static to avoid propagating this.
    
    void empty_helper(Node* head) {
            if (head->next) { empty_helper(head->next); }
            delete head;
            head = nullptr;
        }
    };
    

    当然还有其他选项可用,例如使其非递归。

    在我看来,在这种情况下不需要 lambda。

    【讨论】:

    • lambda 的性能是否比默认函数差?过度使用它们是一种不好的做法?
    • @AlexeiSavitsky 一点也不。它们只是一种方便的语法。它们提供了为一次性功能创建一个非常本地化的名称的方法。
    猜你喜欢
    • 2018-03-26
    • 1970-01-01
    • 2018-06-25
    • 2020-03-22
    • 1970-01-01
    • 2014-09-05
    • 2010-10-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多