Реализация итератора связанного списка C ++ - PullRequest
1 голос
/ 28 марта 2019

Я создал связанный список в C ++ и хочу реализовать для него итератор, чтобы я мог выполнять циклы диапазона: for (const int& i : list), где 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->next == tail? Если да, то как мне это сделать? Потому что у моего Итератора нет объекта списка с хвостом.

Редактировать : Я не уверен, как реализовать struct Iterator;, я застреваю, когда выясняю, как связать его со списком, чтобы я мог проверить, равен ли текущий узел, возвращенный из итератора, хвосту в списке, в Linked_List Iterator end() const метод.

Допустим, я реализовал все необходимые операторы для итератора следующим образом:

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<T>::begin() const; и end()?

Я представляю себе воображаемого пользователя, который делает объект итератора следующим образом: Linked_List<int>::Iterator it;

Идея состоит в том, чтобы иметь открытый конструктор без параметров и закрытый конструктор, который принимает узел в качестве параметра, для которого будет установлен _current, и иметь класс Linked_List в качестве друга.

1 Ответ

1 голос
/ 28 марта 2019

Несколько заметок.

Существует два варианта объявления Node и Iterator. Класс внутри списка как List<T>::Node или снаружи как Node<T>. Отчасти это вопрос вкуса. Однако с технической точки зрения имена символов длиннее для вложенных классов, поэтому ваш debuginfo больше. Кроме того, когда вложенные классы также являются шаблонами, их сложнее специализировать, если / когда это необходимо (поскольку для этого сначала требуется полная специализация включающего шаблона), но здесь это не так.

Это приводит к более элегантному коду, когда один узел списка используется в качестве заголовка и хвоста списка. Пустой список - это узел, чьи next и prev указывают на себя. 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 сам по себе является минималистским списком, который можно использовать для навязчивых списков (с автоотключением в деструкторе). Обратите внимание, что использование 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';
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...