【问题标题】:Linked List Iterator Implementation C++链表迭代器实现 C++
【发布时间】:2019-03-28 16:39:32
【问题描述】:

我在 C++ 中创建了一个链接列表,并希望为它实现一个迭代器,以便我可以进行范围循环:for (const int& i : list) where Linked_List<int> list;

我的想法是创建 Iterator 作为 Linked_List 类的一部分,如下所示:

这是我目前得到的:

template <typename T>
class Linked_List
{
public:
    struct Iterator;
    struct Node;
public:
    Linked_List();
    ~Linked_List() noexcept(false);
    Linked_List(const Linked_List&) = delete;
    Linked_List(Linked_List&&) = delete;
    Linked_List& operator=(const Linked_List&) = delete;
    Linked_List& operator=(Linked_List&&) = delete;

    void push_back(T);
    void push_front(T);
    void pop_back();
    void pop_front();
    bool empty() const;
    T back() const;
    T front() const;
    //void swap(T, T);
    //void insert(Iterator, T);
    //void erase(Iterator);
    //Iterator begin() const;
    //Iterator end() const;
private:
    Node* head;
    Node* tail;
};

template<typename T>
struct Linked_List<T>::Node
{
    Node() : prev(nullptr), next(nullptr) {}
    Node(T t) : value(t), prev(nullptr), next(nullptr) {}
    Node* prev;
    Node* next;
    T value;
};
  1. 这是个好方法吗?
  2. 在增加列表以检查是否current-&gt;next == tail 时是否应该进行错误检查?如果是这样,我该怎么做?因为我的迭代器没有带尾的列表对象。

编辑: 我不确定如何实现struct Iterator;,我在弄清楚如何将它与列表连接时卡住了,以便我可以检查从迭代器返回的当前节点是否等于列表中的尾部,在 Linked_List @ 987654330@方法。

假设我已经为这样的迭代器实现了所有必要的运算符:

struct Iterator
{
    T& operator*() const { return current->value; }
    bool operator!=(const Iterator& rhs) { return (*_current != rhs._current); }
    Iterator& operator++()
    {
        current = current->next;
        return *this;
    }
};

现在我将如何实施Iterator Linked_List&lt;T&gt;::begin() const;end()

我想象一个虚构的用户制作这样的迭代器对象: Linked_List&lt;int&gt;::Iterator it;

一个想法是有一个不带参数的公共构造函数和一个将节点作为参数的私有构造函数,_current 将被设置为,并有 Linked_List 类作为朋友。

【问题讨论】:

  • 1/ 没关系。如果您最终得到多个具有类似迭代器实现的容器,那么可能值得考虑这一点。 2/ 你不需要(这是用户错误,不是正常的运行条件)。但是,如果您愿意,(可能在 DEBUG 构建中)然后考虑最后一个元素的 next 指针应该是什么样子。实际上,考虑一下你将如何在没有哨兵节点的情况下实现 one-past-the-end 迭代器。
  • 这可能会导致您的需求过大,但Writing your own STL Container 应该让您了解您可能需要考虑的一些事情。
  • 目前有效吗?如果是,您最好将完整代码发布到 CodeReview。那里的人非常擅长全面审查您的代码以找到可能的改进
  • 旁注:你几乎肯定需要一个析构函数来进行清理,如果是这样,the Rules of Three and Five 就会生效。
  • @Tzalumen 学习

标签: c++ data-structures iterator


【解决方案1】:

一些笔记。

有两个选项可以声明NodeIterator。在列表类内部为List&lt;T&gt;::Node 或在外部为Node&lt;T&gt;。部分原因是品味问题。但是从工程的角度来看,嵌套类的符号名称更长,因此您的调试信息更大。此外,当嵌套类也是模板时,如果/在必要时更难对它们进行特化(因为这需要首先完全特化封闭模板),但这里不是这种情况。

当使用一个列表节点作为列表头和尾时,它会导致更优雅的代码。空列表是其nextprev 指向自身的节点。 push_front 附加到list.next,它指向第一个节点或它自己。 push_back 将一个节点附加到list.prev,它指向最后一个节点或它本身。在插入/删除节点时,不需要对第一个和最后一个节点进行特殊处理。例如。 :

struct Node {
    Node *next_, *prev_;

    Node() 
        : next_(this), prev_(this)
    {}

    ~Node() { 
        unlink();
    }

    void push_back(Node* n) {
        n->next_ = this;
        n->prev_ = prev_;
        prev_->next_ = n;
        prev_ = n;
    }

    void unlink() {
        Node *next = next_, *prev = prev_;
        next->prev_ = prev;
        prev->next_ = next;
        next_ = this;
        prev_ = this;
    }
};

在上面,Node 只需要两个操作就可以维护一个列表。不仅如此,Node 本身就是一个极简列表,可用于 intrusive 列表(在析构函数中具有自动取消链接)。请注意使用 this 如何使检查 nullptr 变得不必要 - Node 始终是一个有效列表。

错误检查应仅在调试模式下进行(例如,使用assert)。否则,这些检查会通过不必要的运行时检查来惩罚正确的应用程序。


这是一个基于您的想法的最小工作示例:

template<class T>
class List;

class Iterator;

class Node {
    friend class Iterator;
    template<class T> friend class List;

protected:
    Node *next_, *prev_;

    void push_back(Node* n) {
        n->next_ = this;
        n->prev_ = prev_;
        prev_->next_ = n;
        prev_ = n;
    }

    void unlink() {
        Node *next = next_, *prev = prev_;
        next->prev_ = prev;
        prev->next_ = next;
        next_ = this;
        prev_ = this;
    }

public:
    Node()
        : next_(this), prev_(this)
    {}

    ~Node() { unlink(); }
};

class Iterator {
protected:
    Node* node_;

    Iterator(Node* node)
        : node_(node)
    {}

public:
    Iterator& operator++() {
        node_ = node_->next_;
        return *this;
    }

    bool operator==(Iterator b) const { return node_ == b.node_; }
    bool operator!=(Iterator b) const { return node_ != b.node_; }

    // Implement the rest of iterator interface.
};

template<class T>
class List {
    class NodeT : public Node {
        friend class List<T>;
        T value_;
        NodeT(T t) : value_(t) {}
    };

    template<class U>
    class IteratorT : public Iterator {
        friend class List<T>;
        NodeT* node() const { return static_cast<NodeT*>(node_); }
    public:
        U& operator*() const { return node()->value_; }
        U* operator->() const { return &node()->value_; }
        operator IteratorT<U const>() const { return node_; } // iterator to const_iterator conversion
        IteratorT(Node* node) : Iterator{node} {}
    };

    Node list_;

public:
    using iterator = IteratorT<T>;
    using const_iterator = IteratorT<T const>;

    ~List() { clear(); }

    bool empty() const { return list_.next_ == &list_; }

    iterator begin() { return list_.next_; }
    iterator end() { return &list_; }

    void push_back(T t) { list_.push_back(new NodeT(t)); }
    void erase(const_iterator i) { delete i.node(); }

    void clear() {
        while(!empty())
            erase(begin());
    }

    // Implement the rest of the functionality.
};

int main() {
    List<int> l;
    l.push_back(1);
    l.push_back(2);
    l.push_back(3);
    for(auto elem : l)
        std::cout << elem << ' ';
    std::cout << '\n';
}

【讨论】:

  • 节点中的unlink() 是天才。但是节点应该有push_back() 吗?真的谢谢你所做的一切。
  • @user644361 unlinkpush_back 是所有其他操作都基于的两个低级基本操作。
  • @user644361 请注意,我没有用publicprivate 封装它。 Node::unlinkNode::push_back 只能由 List 类访问。用户不应直接调用它们。您可以根据需要添加private/public,我只专注于获得正确的功能。此外,Iterator::node 不应被用户调用。
  • @user644361 是的,都是私有的,除非它必须是公开的。让List 成为NodeTIteratorT 的朋友。
  • @user644361 确实如此。 std::list 也是如此。但是您可以这样做 list.erase(it++)it 是下一个元素的有效迭代器。这就是std::list::erase 返回下一个迭代器的方式。
猜你喜欢
  • 2018-09-10
  • 2016-09-27
  • 1970-01-01
  • 2011-11-14
  • 1970-01-01
  • 1970-01-01
  • 2011-09-28
  • 1970-01-01
相关资源
最近更新 更多