【问题标题】:How to implement the two member functions (push_front and the destructor)?如何实现两个成员函数(push_front 和析构函数)?
【发布时间】:2021-07-31 08:40:04
【问题描述】:

我正在尝试实现两个成员函数,即push_frontdestructor。我已经编写了push_front 函数的代码。但我似乎,我在任何地方都做错了。我该如何解决这个问题?如何在Linked-List前面正确插入节点?

template <typename T>
class Node {
public:
    Node():next_p_{nullptr} {}
    Node(T v, Node* n):data_{v}, next_p_{n} {}
   ~Node() {};

    void     setData( T &v ) { data_ = v; }
    const T &getData() const { return data_; }
    void  setNext( Node *n ) { next_p_ = n; }
    Node *getNext() const { return next_p_; }

private:
  T data_;
  Node *next_p_;
};

template <typename T>
class LinkedList {
private:
  Node<T> *head_ptr_;
public:
    LinkedList():head_ptr_{nullptr} {}
   ~LinkedList() {
    }

    // Insert at the front of the linked list (UPDATED)
    void push_front( T v ){
        Node *new_node = new Node;
        new_node->setNext(*head_ptr_) = v;
        new_node->next_p_ = this->head_ptr_;
        this->head_ptr_ = new_node;
    }
};

【问题讨论】:

  • 你插入的不是数字,而是节点。由于Node是一个模板类,它可以包含任何类型的数据。
  • 你创建一个新的Node,设置它的next指针指向旧的head_ptr,然后设置head_ptr指向新的Node
  • 单链表 push_front 涉及创建一个新节点,将其 next 指针设置为当前链表头指针,然后将链表头指针设置为刚刚分配的新节点。销毁链表只是对所有节点的枚举,随着您的进行销毁它们。有了这个,你最好阅读What is the Rule Of Three?。它与您的代码高度相关。
  • @Barmar,感谢您的评论。请详细发布它作为答案以便更好地理解。
  • 如果不写代码为你做功课,我不知道如何更详细地解释。没有比我之前说的更多的了。

标签: c++ linked-list singly-linked-list


【解决方案1】:

第一个Node 构造函数获取值和下一个指针,使用它一步创建新节点。

在将head_ptr_ 用作下一个指针时,不应取消引用它。它已经是Node*,这是head_ptr_ 的正确类型。

    void push_front( T v ){
        Node *new_node = new Node(v, head_ptr_);
        this->head_ptr_ = new_node;
    }

【讨论】:

  • 或者,如果感觉特别简短,head_ptr_ = new Node(v, head_ptr_);
【解决方案2】:

我还建议不要处理手动内存管理,而是使用智能指针来简化您的生活。

使用std::unique_ptr,您无需处理从空闲存储中回收分配的内存。

这种方法的一个警告是您无法复制List,但通常这就是您想要的。

template<typename T>
class List { 
    struct Node { 
        Node( T value ) noexcept
            : data{ std::move_if_noexcept( value ) }
        { }

        T data;
        std::unique_ptr<Node> next{ nullptr };
    };
public:
    auto push_front( T value ) -> void {
        auto node{ std::make_unique<Node>( std::move_if_noexcept( value ) ) };
        std::swap( node->next, head_ );
        std::swap( head_, node );
    }

    ~List()
    {
        while (head_) {
            std::unique_ptr<Node> tmp(std::move(head_));
            head_ = std::move(tmp->next);
        }
    }
private:
    std::unique_ptr<Node> head_{ nullptr };
}; 

【讨论】:

  • 这样做时要小心。创建一个包含数千个(或更多)节点的链表是微不足道的,这会导致递归删除操作的深度与列表的长度一样深,从而导致列表被破坏。它可以(并且如果允许的话)会消除线程的激活堆栈,然后您会感到难过,因为您的程序实际上将“脱离深渊”。一个用户定义的析构函数配备了一组巧妙的针对头指针的移动操作和一个非常局部的临时唯一指针,可以纠正这种情况(是的,还有其他方法)。
  • @WhozCraig 是因为 Node 中对 self 的引用吗?
  • 这是因为一个实例在其成员被销毁之前还没有被销毁。然后整个节目转移到基地,但这在这里并不重要。想象头指针指向的节点。要销毁该实例,它的成员(特别是它的 next 指针)也必须被销毁。冲洗,重复,每次递归,直到我们到达列表末尾,它有一个未绑定的下一个。您已经为 N 长度的列表递归了 N-1 次,如果您设法在不破坏调用堆栈的情况下幸存下来,那么整个事情就会展开,并在退出的每一步完成实例销毁。
  • @WhozCraig 啊,我现在明白了。这也澄清了有人在 Herbs 关于atomic 的谈话中提出的一个问题,他使用std::atomic&lt;std::shared_ptr&gt; 来避免ABA 问题的LinkedList 实现。
  • 我将添加一个示例,说明如何在您的代码中解决此问题。它是一个简单的析构函数。正如我所说,有多种方法可以做到这一点,但这是一种更巧妙的语言方法。
猜你喜欢
  • 1970-01-01
  • 2015-06-13
  • 2010-10-02
  • 2011-01-16
  • 1970-01-01
  • 2011-04-06
  • 2020-08-01
  • 2019-03-09
  • 2012-10-17
相关资源
最近更新 更多