Привет Хабр, наверное все, кто изучает С++ хотят разобраться как реализованы и как работают классы-контейнеры из стандартной библиотеки. Как по мне, чтобы лучше освоить нечто похожее на контейнеры, то надо попробовать реализовать один из контейнеров самому. В этой статье я хочу показать вам хотя бы примерно как реализуются классы-контейнеры на примере списка. Хочу сразу сказать, что это не будет копирование всего функционала, а будет показана только концепция работы контейнера, а именно реализуем класс списка и класс итератора для работы с ним.
Статья будет интересна только новичкам, которые начинают изучать стандартную библиотеку, профессионалы не найдут здесь для себя ничег�� новго.
Ну что ж, начнем. Что собой представляет list из стандартной библиотеки? Это последовательный контейнер, который оптимизирован для вставки и удаления элементов. Для работы с этим контейнером в STL используется двунаправленный итератор, который мы попробуем реализовать. Также мы реализуем функцию вставки на начало и в конец списка, вставку после элемента на который указывает iterator, удаление элементов и еще несколько функций.
А сейчас будет много кода с комментариями.
файл «dlist.h»
Использование списка
Итератор реализован как открытый вложенный класс. Так как класс открытый, пользователи могут создавать объекты. Класс iterator должен знать о некоторых закрытых элементах класса Double_list, поэтому мы объявляем класс iterator дружественным к классу Double_list и так же в классе iterator объявляем другом класс Double_list.
Теперь посмотрим на внутреннее устройство класса Double_list::iterator. У него есть единственный элемент данных: Double_node* the_node. Именно это итератор и должен скрывать. Операции, объявляемые в классе iterator, позволяют пользователям манипулировать этим узлом определенным образом.
Вот на этом и все. Примерно так реализован ��ласс списка в библиотеке STL. Конечно, наш класс очень общий пример, в STL все сложнее, но общий принцип с этого кода понять можно.
Статья будет интересна только новичкам, которые начинают изучать стандартную библиотеку, профессионалы не найдут здесь для себя ничег�� новго.
Ну что ж, начнем. Что собой представляет list из стандартной библиотеки? Это последовательный контейнер, который оптимизирован для вставки и удаления элементов. Для работы с этим контейнером в STL используется двунаправленный итератор, который мы попробуем реализовать. Также мы реализуем функцию вставки на начало и в конец списка, вставку после элемента на который указывает iterator, удаление элементов и еще несколько функций.
А сейчас будет много кода с комментариями.
файл «dlist.h»
#ifndef DLIST_H_
#define DLIST_H_
#include <iostream>
template <typename T>
class Double_list
{
public:
class iterator;
friend class iterator; //класс итератора должен иметь доступ к полям класса Double_list
private:
class Double_node;
friend class Double_node;
class Double_node //
{
public:
friend class Double_list<T>;
friend class iterator;
//Создать вузол с какимто значением типа T
Double_node(T node_val): val(node_val) {}
Double_node() {}
~Double_node() {}
void print_val() {std::cout << val << " "; }
Double_node *next; //указывает на следующий узел списка
Double_node *prev; //указывает на предыдущий узел.
T val; //данные.
};
public:
class iterator
{
friend class Double_list<T>;
public:
// нулевой конструкрор
iterator() :the_node(0) {}
//здесь мы создаем итератор с указателя на узел Double_node
iterator(Double_node *dn): the_node(dn) {}
//конструктор копии
iterator(const iterator &it): the_node(it.the_node) {}
iterator& operator=(const iterator &it)
{
the_node = it.the_node;
return *this;
}
// в этом классе оператор == означает, что два итератора указывают на
// один и тот же узел
bool operator == (const iterator &it) const
{
return (the_node == it.the_node);
}
bool operator!=(const iterator &it) const
{
return !(it == *this);
}
//переводит итератор на следующий узел списка.
iterator& operator++()
{
if (the_node == 0)
throw "incremented an empty iterator";
if (the_node->next == 0)
throw "tried to increment too far past the end";
the_node = the_node->next;
return *this;
}
//переводит итератор на предідущий узел списка.
iterator & operator--()
{
if (the_node == 0)
throw "decremented an empty iterator";
if (the_node->prev == 0)
throw "tried to decrement past the beginning";
the_node = the_node->prev;
return *this;
}
//Возвращает значение данных.
T& operator*() const
{
if (the_node == 0)
throw "tried to dereference an empty iterator";
return the_node->val;
}
private:
Double_node *the_node;
};
private:
Double_node *head; //указывает на начало списка.
Double_node *tail; //указывает на елемент, который идет за последним
iterator head_iterator; //итератор, который всегда указывает на начало списка
iterator tail_iterator; //итератор, который всегда указывает на элемент, который находится за последним.
public:
Double_list()
{
head = tail = new Double_node;
tail->next = nullptr;
tail->prev = nullptr;
//инициализирует итераторы
head_iterator = iterator(head);
tail_iterator = iterator(tail);
}
//Создать список, который содержит один элемент.
Double_list(T node_val)
{
head = tail = new Double_node;
tail->next = nullptr;
tail->prev = 0;
head_iterator = iterator(head);
tail_iterator = iterator(tail);
add_front(node_val);
}
~Double_list()
{
Double_node *node_to_delete = head;
for (Double_node *sn = head; sn != tail;)
{
sn = sn->next;
delete node_to_delete;
node_to_delete = sn;
}
delete node_to_delete;
}
bool is_empty() {return head == tail;}
iterator front() {return head_iterator;}
iterator rear() {return tail_iterator;}
//вставляем узел в начало списка
void add_front(T node_val)
{
Double_node *node_to_add = new Double_node (node_val);
node_to_add->next = head;
node_to_add->prev = nullptr;
head->prev = node_to_add;
head = node_to_add;
//так как head изменился, нужно изменить head_iterator
head_iterator= iterator(head);
}
//вставляем узел в конец списка
void add_rear(T node_val)
{
if (is_empty())
add_front(node_val);
else
{
Double_node *node_to_add = new Double_node(node_val);
node_to_add->next = tail;
node_to_add->prev = tail->prev;
tail->prev->next = node_to_add;
tail->prev = node_to_add;
//изменяем tail_iterator
tail_iterator = iterator(tail);
}
}
//вставляет в спсиок node_val после итератора key_i
bool insert_after(T node_val, const iterator &key_i)
{
for (Double_node *dn = head; dn != tail; dn = dn->next)
{
//Найден ли узел для заданного итератора
if (dn == key_i.the_node)
{
Double_node *node_to_add = new Double_node(node_val);
node_to_add->prev = dn;
node_to_add->next = dn->next;
dn->next->prev = node_to_add;
dn->next = node_to_add;
return true;
}
}
return false;
}
//Удалить первый элемент списка.
T remove_front()
{
if (is_empty())
throw "tried to remove from an empty list";
Double_node *node_to_remove = head;
T return_val = node_to_remove->val;
head = node_to_remove->next;
head->prev = 0;
head_iterator = iterator(head);
delete node_to_remove;
return return_val;
}
T remove_rear()
{
if (is_empty())
throw "tried to remove from an empty list";
Double_node *node_to_remove = tail->prev;
if(node_to_remove->prev == 0)
{
return remove_front();
}
else
{
T return_val = node_to_remove->val;
node_to_remove->prev->next = tail;
tail->prev = node_to_remove->prev;
delete node_to_remove;
return return_val;
}
}
bool remove_it(iterator &key_i)
{
for (Double_node *dn = head; dn != tail; dn = dn-next)
{
//Найден ли узел для заданного итератора
if (dn == key+i.the_node)
{
dn->prev->next = dn->next;
dn->next->prev = dn->prev;
delete dn;
key_i.the_node =0;
return true;
}
}
return false;
}
//Возвращает первый итератор, указывающий на node_val
iterator find(T node_val) const
{
for (double_node *dn = head; dn != tail; dn = dn->next)
{
if (dn->val == node_val)
return iterator(dn);
}
//Если node_val нет в списке возвращает tail_iterator
return tail_iterator;
}
//возвращает итератор, который указывает на n-ый ��лемент списка
iterator get_nth(const int element_num) const
{
if (element_num < 1)
return tail_iterator;
int count = 1;
for(Double_node *dn = head; dn != tail; dn = dn->next)
{
if (count++ == element_num)
return iterator(dn);
}
return tail_iterator;
}
//Количество узлов в списке.
int size() const
{
int count = 0;
for (Double_node *dn = head; dn != tail; dn = dn->next)
++count;
return count;
}
void print() const
{
for (Double_node *dn = head; dn!= tail; dn = dn->next)
{
dn->print_val();
}
std::cout << std::endl;
}
};
#endif
Использование списка
#include "dlist.h"
int main()
{
Double_list<int> the_list;
Double_list<int>::iterator list_iter;
for (int i=0; i<5; ++i)
{
the_list.add_front(i);
}
the_list.print();
the_list.remove_front();
for (list_iter = the_list.front(); list_iter != the_list.rear(); ++ list_iter)
{
std::cout << *list_iter << " ";
}
std::cout << std:: endl;
//выводим в обратном порядке
for (list_iter = the_list.rear(); list_iter != the_list.front();)
{
--list_iter;
std::cout << *list_iter << " ";
}
std::cout << std::endl;
system("PAUSE");
return 0;
}
Анализ кода
Итератор реализован как открытый вложенный класс. Так как класс открытый, пользователи могут создавать объекты. Класс iterator должен знать о некоторых закрытых элементах класса Double_list, поэтому мы объявляем класс iterator дружественным к классу Double_list и так же в классе iterator объявляем другом класс Double_list.
Теперь посмотрим на внутреннее устройство класса Double_list::iterator. У него есть единственный элемент данных: Double_node* the_node. Именно это итератор и должен скрывать. Операции, объявляемые в классе iterator, позволяют пользователям манипулировать этим узлом определенным образом.
Конец
Вот на этом и все. Примерно так реализован ��ласс списка в библиотеке STL. Конечно, наш класс очень общий пример, в STL все сложнее, но общий принцип с этого кода понять можно.
