Линейный двухсвязный список. Пример шаблонного класса
В данной теме приводится пример разработки класса, реализующего линейный двухсвязный список. Обработав и скопировав каждый из пунктов данной темы, можно получить работоспособный, протестированный программный код класса, реализующий двухсвязный список.
Общие сведения и особенности линейных двухсвязных списков можно найти в теме:
Содержание
- 1. Общее описание составляющих прогаммы
- 2. Общая структура программы (сокращенный код)
- 3. Составляющие программы (подробный обзор)
- 3.1. Структура Element<T>
- 3.2. Класс List<T>. Программный код
- 3.2.1. Внутренние поля класса
- 3.2.2. Дополнительные внутренние методы класса
- 3.2.3. Специальные функции класса
- 3.2.3.1. Конструктор List<T>(). Создание пустого списка
- 3.2.3.2. Конструктор копирования List<T>(const List<T>& obj). Копирование списка при его присваивании с одновременной инициализацией
- 3.2.3.3. Оператор копирования operator=(const List<T>& obj). Присваивание списков
- 3.2.3.4. Деструктор ~List<T>(). Удаление (очистка) списка
- 3.2.4. Методы GetElement(int) и SetElement(T, int). Доступ к отдельным элементам списка
- 3.2.5. Методы, изменяющие список в целом
- 3.2.5.1. Метод AddEnd(T). Добавить элемент в конец списка
- 3.2.5.2. Метод AddBegin(T). Добавить элемент в начало списка
- 3.2.5.3. Метод Insert(T, int). Вставка элемента в заданную позицию списка
- 3.2.5.4. Метод Del(int). Удалить элемент в заданной позиции
- 3.2.5.5. Метод Clear(). Очистка списка
- 3.2.5.6. Метод Reverse(). Реверсирование списка
- 3.2.6. Метод Print(string). Вывод списка
- 3.2.7. Метод Count(). Получить количество элементов в списке
- 3.2.8. Реализация базовых операций в списках
- 3.2.8.1. Операторная функция operator+(const List<T>& obj). Перегрузка оператора +. Конкатенация списков
- 3.2.8.2. Операторная функция operator==(const List<T>& obj). Перегрузка оператора ==. Сравнение списков на равенство
- 3.2.8.3. Операторная функция operator!=(const List<T>& obj). Перегрузка оператора !=. Сравнение списков на неравенство
- 3.2.8.4. Операторная функция operator>=(const List<T>& obj). Перегрузка оператора >=
- 3.2.8.5. Операторная функция operator<=(const List<T>& obj). Перегрузка оператора <=
- 3.2.8.6. Операторная функция operator>(const List<T>& obj). Перегрузка оператора >
- 3.2.8.7. Операторная функция operator<(const List<T>& obj). Перегрузка оператора <
- 3.2.9. Функция main(). Клиентский код
- 4. Запуск программы. Результаты
- Связанные темы
Поиск на других ресурсах:
1. Общее описание составляющих прогаммы
Разработанная программа носит демонстрационный характер. При создании приложения использовался тип приложения Console Application.
В разработанном примере объявляются следующие программные элементы:
- шаблонная структура Element<T>, описывающая один элемент списка;
- шаблонный класс List<T>, реализующий двухсвязный список структур типа Element<T>.
Структура Element<T> содержит следующие поля:
- data – поле, являющееся данными;
- next – указатель на следующий элемент списка;
- prev – указатель на предыдущий элемент списка.
В шаблонном классе List<T> реализованы следующие элементы:
- внутренние поля begin и end типа Element<T>*. Это указатели на первый и последний элемент списка;
- внутреннее поле count – количество элементов в списке;
- внутренний скрытый (private) метод Move(int) – возвращает элемент списка по заданному индексу;
- внутренний скрытый (private) метод Copy(const List&) – осуществляет копию из списка;
- внутренний скрытый (private) метод CorrectIndex(int) – определяет, имеет ли позиция (индекс) элемента списка корректное значение;
- конструктор List();
- конструктор копирования List(const List&);
- оператор копирования operator=(const List&);
- деструктор List~();
- метод GetElement(int) – возвращает элемент по заданному индексу;
- метод SetElement(T, int) – устанавливает значение элемента по заданному индексу;
- метод AddEnd(T) – добавляет новый элемент в конец списка;
- метод AddBegin(T) – добавляет новый элемент в начало списка;
- метод Insert(T, int) – вставка элемента в заданную позицию списка;
- метод Del(int) – удалить элемент из заданной позиции списка;
- метод Clear() – очистка списка;
- метод Reverse() – реверсирование списка;
- метод Print() – вывод списка;
- метод Count() – вернуть количество элементов в списке;
- операторная функция operator+(const List&) – конкатенация списков;
- операторная функция operator==(const List&) – сравнение списков на равенство;
- операторная функция operator!=(const List&) – сравнение списков на неравенство;
- операторная функция operator>=(const List&) – сравнение списков;
- операторная функция operator<=(const List&) – сравнение списков;
- операторная функция operator>(const List&) – сравнение списков;
- операторная функция operator<(const List&) – сравнение списков.
⇑
2. Общая структура программы (сокращенный код)
Сокращенный программный код приложения имеет вид, показанный ниже. Каждую составляющую программы можно получить, проработав все последующие пункты данной темы.
#include <iostream> using namespace std; // Структура, описывающая один элемент (узел) template <class T> struct Element { // Составляющие структуры Element<T> }; // Клас, реализующий двусвязный список template <class T> class List { // Составляющие класса List<T> // ... }; // Реализация методов класса List<T> // ... void main() { // Клиентский код (тестирование) // ... }
⇑
3. Составляющие программы (подробный обзор)
3.1. Структура Element<T>
В программе объявление структуры имет вид:
// Структура, описывающая один елемент (узел) template <class T> struct Element { T data; // данные Element<T>* next; // адрес следующего элемента в списке Element<T>* prev; // адрес предыдущего элемента в списке };
⇑
3.2. Класс List<T>. Программный код
В данной реализации класса List<T> определены только сигнатуры (прототипы) методов, которые в нем реализованы. Сами реализации методов класса описываются в следующих пунктах.
Программный код класса следующий.
// Класс, реализующий двусвязный список template <class T> class List { private: Element<T>* begin; // указатель на первый элемент списка Element<T>* end; // указатель на последний элемент списка int count; // количество элементов в списке // Метод, возвращающий элемент в заданной позиции, // считается что позиция корректна. Element<T>* Move(int index); // Метод, копирующий список void Copy(const List<T>& obj); // Метод, проверяющий корректность позиии (индекса) в списке bool CorrectIndex(int index); public: // Конструктор List(); // Конструктор копирования List(const List& obj); // Оператор копирования List<T>& operator=(const List& obj); // Деструктор ~List(); // ---------- Методы доступа к отдельным элементам списка -------- // Получить элемент списка по индексу T GetElement(int index); // Изменить значение элемента в заданной позиции void SetElement(T _data, int index); // ---------- Методы изменения размера списка ------------ // Добавить элемент в конец списка void AddEnd(T _data); // Добавить элемент в начало списка void AddBegin(T _data); // Вставка элемента в заданную позицию списка void Insert(T _data, int index); // Удалить элемент в заданной позиции, // позиция нумеруется с 0 void Del(int index); // Очистка списка void Clear(); // Реверсирование списка void Reverse(); // Вывод списка void Print(string msg); // Получить количество элементов списка int Count(); // ------------------------------------------ // Перегрузка операторов // Операция + - конкатенация списков List<T>& operator+(const List<T>& obj); // Операция сравнения списков на равенство bool operator==(const List& obj); // Операция сравнения списков на неравенство bool operator!=(const List& obj); // Операция >= bool operator>=(const List& obj); // Операция <= bool operator<=(const List& obj); // Операция > bool operator>(const List& obj); // Операция < bool operator<(const List& obj); };
⇑
3.2.1. Внутренние поля класса
В классе List<T> линейный список представлен тремя внутренними полями:
- begin – указатель на начало списка;
- end – указатель на конец списка;
- count – количество элементов списка.
// Класс, реализующий двусвязный список template <class T> class List { private: Element<T>* begin; // указатель на первый элемент списка Element<T>* end; // указатель на последний элемент списка int count; // количество элементов в списке ... }
⇑
3.2.2. Дополнительные внутренние методы класса
В классе выделен ряд операций (методов), используемых в разных методах. Эти методы объявляются в разделе private.
3.2.2.1. Метод Move(int). Получить элемент в заданной позиции
Метод Move(int) возвращает указатель на элемент списка по заданной позиции.
// Получить элемент в заданной позиции template <class T> Element<T>* List<T>::Move(int index) { // 1. Установить указатель на начало списка Element<T>* t = begin; // 2. Перемотать в позицию index for (int i = 0; i < index; i++) t = t->next; // 3. Вернуть указатель return t; }
⇑
3.2.2.2. Метод Copy(const List<T>&). Сделать копию из списка
Для текущего экземпляра метод Copy() реализует копию из списка, являющегося входным параметром obj.
// Метод, делающий копию из списка template <class T> void List<T>::Copy(const List<T>& obj) { // 1. Очистить список (освободить память) Clear(); // 2. Цикл копирования this <= obj Element<T>* t = obj.begin; while (t != nullptr) { AddEnd(t->data); t = t->next; } }
⇑
3.2.2.3. Метод CorrectIndex(int). Проверка корректности позиции (индекса) в списке
Метод CorrectIndex() получает входным параметром позицию index и возвращает true, если позиция корректна, то есть лежит в диапазоне [0..count-1]. В противном случае метод возвращает false.
// Метод, проверяющий корректность позиции (индекса) в списке template <class T> bool List<T>::CorrectIndex(int index) { return (index >= 0) && (index < count); }
⇑
3.2.3. Специальные функции класса
3.2.3.1. Конструктор List<T>(). Создание пустого списка
Для создания пустого списка используется конструктор без параметров, программный код которого имеет следующий вид.
// Конструктор template <class T> List<T>::List() { // Создать пустой список begin = end = nullptr; count = 0; }
⇑
3.2.3.2. Конструктор копирования List<T>(const List<T>& obj). Копирование списка при его присваивании с одновременной инициализацией
При объявлении объекта класса List<T> и одновременном присваивании ему экземпляра другого объекта вызывается конструктор копирования.
// Конструктор копирования template <class T> List<T>::List(const List& obj) { // Сделать копию из списка Copy(obj); }
⇑
3.2.3.3. Оператор копирования operator=(const List<T>& obj). Присваивание списков
Оператор копирования вызывается при присваивании экземпляров класса.
// Оператор копирования template <class T> List<T>& List<T>::operator=(const List& obj) { Copy(obj); return *this; }
⇑
3.2.3.4. Деструктор ~List<T>(). Удаление (очистка) списка
В деструкторе происходит освобождение памяти, выделенной под список, путем вызова метода Clear().
// Деструктор template <class T> List<T>::~List() { Clear(); // очистить список }
⇑
3.2.4. Методы GetElement(int) и SetElement(T, int). Доступ к отдельным элементам списка
Метод GetElement() возвращает элемент списка по заданному индексу. В отличие от метода Move() здесь производится проверка значения index на корректность. Если значение index некорректно, то создается исключительная ситуация типа out_of_range.
Метод SetElement() изменяет значение элемента списка по заданному индексу.
// Получить элемент списка по индексу template <class T> T List<T>::GetElement(int index) { // Проверка, корректен ли индекс, // если индекс не корректен, сгенерировать исключение if (!CorrectIndex(index)) throw out_of_range("Incorrect index."); // Если индекс корректен, то вернуть элемент Element<T>* t = Move(index); return t->data; } // Изменить значение элемента в указанной позиции template <class T> void List<T>::SetElement(T _data, int index) { // Проверка, корректна ли позиция if (!CorrectIndex(index)) return; // Получить элемент по позиции и изменить его значение Element<T>* t = Move(index); t->data = _data; }
⇑
3.2.5. Методы, изменяющие список в целом
3.2.5.1. Метод AddEnd(T). Добавить элемент в конец списка
Метод AddEnd() добавляет новый элемент в конец списка.
// Добавить элемент в конец списка template <class T> void List<T>::AddEnd(T _data) { try { // 1. Создать новый элемент с данными _data Element<T>* t = new Element<T>; t->next = nullptr; // следующего элемента нет t->prev = end; // установить предыдущий элемент t->data = _data; // записать данные // 2. Заполнить поле next пока что последнего элемента if (end != nullptr) end->next = t; // 3. Проверка, есть ли в списке элементы if (count == 0) { // если элементов нет, // то это одновременно и начало и конец списка begin = end = t; } else { // если элементы в списке есть, то это конец списка end = t; } // 3. Увеличить общее количество элементов count++; } catch (bad_alloc e) { // Если память не выделена, то вывести системное сообщение cout << e.what() << endl; } }
⇑
3.2.5.2. Метод AddBegin(T). Добавить элемент в начало списка
// Добавить элемент в начало списка template <class T> void List<T>::AddBegin(T _data) { try { // 1. Создать новый элемент (новую ячейку памяти) // и заполнить его данными Element<T>* t = new Element<T>; t->data = _data; // данные t->prev = nullptr; // предыдущего элемента нет // следующий элемент указывает на предыдущий первый t->next = begin; // 2. Есть ли элементы в списке? if (count > 0) { // если есть, то перенаправить предыдущее начало списка begin->prev = t; begin = t; } else { // если элементов нет, то начало и конец есть тем самым элементом begin = end = t; } // 3. Увеличить общее количество элементов count++; } catch (bad_alloc e) { // если память не выделена, то обработать ошибку cout << e.what() << endl; } }
⇑
3.2.5.3. Метод Insert(T, int). Вставка элемента в заданную позицию списка
// Вставка элемента в заданную позицию списка template <class T> void List<T>::Insert(T _data, int index) { // 1. Проверка, корректна ли позиция if (!CorrectIndex(index)) return; // 2. Проверка, вставка ли в конец списка (по указателю end) if (index == count) { // Добавить данные по указателю end AddEnd(_data); return; } // 3. Проверка, вставка ли в начало списка (перед begin) if (index == 0) { AddBegin(_data); return; } try { // 4. Получить элемент перед позицией вставки Element<T>* itemPrev = Move(index - 1); // 5. Получить элемент в позиции вставки Element<T>* item = Move(index); // 6. Создать новый элемент и вставить его в список Element<T>* t = new Element<T>; t->data = _data; t->next = item; t->prev = itemPrev; itemPrev->next = t; item->prev = t; // 7. Увеличить количество элементов count++; } catch (bad_alloc e) { // Если память не выделена, то вывести системное сообщение cout << e.what() << endl; } }
⇑
3.2.5.4. Метод Del(int). Удалить элемент в заданной позиции
// Удалить элемент в заданной позиции, // позиция нумеруется с 0 template <class T> void List<T>::Del(int index) { // 1. Проверка, есть ли элементы в списке if (count == 0) return; // 2. Игнор, если позиция указана неправильно if (!CorrectIndex(index)) return; // 3. Перейти к удаляемому элементу Element<T>* item = Move(index); // 4. Получить предыдущий элемент Element<T>* itemPrev = item->prev; // 5. Получить следующий элемент Element<T>* itemNext = item->next; // 6. Проверка, удаляется ли не первый элемент списка if ((count > 1) && (itemPrev != nullptr)) itemPrev->next = itemNext; // обойти элемент item // 7. Проверка, удаляется ли не последний элемент списка if ((itemNext != nullptr) && (count > 1)) itemNext->prev = itemPrev; // 8. Если удаляется первый элемент if (index == 0) begin = itemNext; // 9. Если удаляется последний элемент if (index == count - 1) end = itemPrev; // 10. Удалить элемент item delete item; // 11. Уменьшить общее количество элементов count--; }
⇑
3.2.5.5. Метод Clear(). Очистка списка
// Очистка списка template <class T> void List<T>::Clear() { // Нужно count раз удалить первый элемент списка int n = count; // сделать копию из count - важно! for (int i = 0; i < n; i++) Del(0); }
⇑
3.2.5.6. Метод Reverse(). Реверсирование списка
// Реверсирование списка template <class T> void List<T>::Reverse() { List<T> L; Element<T>* t = begin; // цикл формирования списка, // элемент добавляется в начало списка while (t != nullptr) { L.AddBegin(t->data); t = t->next; } *this = L; }
⇑
3.2.6. Метод Print(string). Вывод списка
С целью определения текущего состояния списка в классе реализован метод Print(). Метод выводит список в удобочитаемой форме.
// Вывод списка template <class T> void List<T>::Print(string msg) { cout << msg << " => "; Element<T>* t = begin; for (int i = 0; i < count; i++) { cout << t->data << " "; t = t->next; } cout << endl; }
⇑
3.2.7. Метод Count(). Получить количество элементов в списке
// Получить количество элементов списка template <class T> int List<T>::Count() { return count; }
⇑
3.2.8. Реализация базовых операций в списках
3.2.8.1. Операторная функция operator+(const List<T>& obj). Перегрузка оператора +. Конкатенация списков
Для реализации удобной конкатенации (сложения) списков, в классе перегружен оператор + с помощью функции operator+().
// Операция + - конкатенация списков template <class T> List<T>& List<T>::operator+(const List<T>& obj) { // 1. Получить доступ к списку obj Element<T>* t = obj.begin; // 2. Добавить к временному списку элементы t while (t != nullptr) { AddEnd(t->data); t = t->next; } // 3. Вернуть объединенный список return *this; }
⇑
3.2.8.2. Операторная функция operator==(const List<T>& obj). Перегрузка оператора ==. Сравнение списков на равенство
Полезными при работе со списками будут операции сравнения. Одной из них является операция сравнения списков на равенство, реализуемая в операторной функции operator==().
// Операция сравнения списков на равенство template <class T> bool List<T>::operator==(const List<T>& obj) { // 1. Сначала сравнить размеры списков if (count != obj.count) return false; // 2. Если размеры одинаковы, то сравнить поэлементно Element<T>* t1 = begin; Element<T>* t2 = obj.begin; while (t1 != nullptr) { // Как только найдено хотя бы одно несовпадение, то выход с кодом false if (t1->data != t2->data) return false; t1 = t1->next; t2 = t2->next; } return true; }
⇑
3.2.8.3. Операторная функция operator!=(const List<T>& obj). Перегрузка оператора !=. Сравнение списков на неравенство
Сравнение списков на неравенство реализовано в операторной функции operator!=(), перегружающей оператор !=.
// Операция сравнения списков на неравенство template <class T> bool List<T>::operator!=(const List<T>& obj) { // Использовать оператор сравнения == return !(*this == obj); } // Операция >= template <class T> bool List<T>::operator>=(const List<T>& obj) { // 1. Сравнить количество элементов if (count > obj.count) return true; // 2. Сравнить по содержанию if (*this == obj) return true; return false; }
⇑
3.2.8.4. Операторная функция operator>=(const List<T>& obj). Перегрузка оператора >=
// Операция >= template <class T> bool List<T>::operator>=(const List<T>& obj) { // 1. Сравнение по количеству элементов if (count > obj.count) return true; // 2. Сравнение по содержанию if (*this == obj) return true; return false; }
⇑
3.2.8.5. Операторная функция operator<=(const List<T>& obj). Перегрузка оператора <=
// Операция <= template <class T> bool List<T>::operator<=(const List<T>& obj) { // 1. Сравнение по количеству элементов в списке if (count < obj.count) return true; // 2. Сравнение по содержанию if (*this == obj) return true; return false; }
⇑
3.2.8.6. Операторная функция operator>(const List<T>& obj). Перегрузка оператора >
// Операция > template <class T> bool List<T>::operator>(const List<T>& obj) { if (count > obj.count) return true; return false; }
⇑
3.2.8.7. Операторная функция operator<(const List<T>& obj). Перегрузка оператора <
// Операція < template <class T> bool List<T>::operator<(const List<T>& obj) { if (count < obj.count) return true; return false; }
⇑
3.2.9. Функция main(). Клиентский код
Тестирование работы класса List<T> осуществляется в функции main(). Здесь объявляется экземпляр класса, для которого вызываются базовые методы.
void main() { // Тест List<double> L1; // Создать список // Добавить элементы в список L1.AddBegin(2.0); L1.AddBegin(1.0); L1.AddEnd(3.0); L1.AddEnd(4.0); L1.Print("L1: "); // Вставить элемент в заданную позицию L1.Insert(-2.5, 2); L1.Print("L1.Insert: "); // Конструктор копирования List<double> L2 = L1; L2.Print("L2: "); // Оператор копирования List<double> L3; L3 = L2; L3.Print("L3: "); // Получить элемент double x = L1.GetElement(3); cout << "x = " << x << endl; // Установить новый элемент L3.SetElement(100.55, 0); L3.Print("L3: "); // Сравнить два списка if (L1 == L3) cout << "L1==L2" << endl; else cout << "L1!=L2" << endl; // Реверсирование списка L1.Reverse(); L1.Reverse(); L1.Print("L1: "); }
⇑
4. Запуск программы. Результаты
После запуска на выполнение программа выдаст следующий результат
L1: => 1 2 3 4 L1.Insert: => 1 2 -2.5 3 4 L2: => 1 2 -2.5 3 4 L3: => 1 2 -2.5 3 4 x = 3 L3: => 100.55 2 -2.5 3 4 L1!=L2 L1: => 1 2 -2.5 3 4 L6: =>
⇑
Связанные темы
- Линейный односвязный список. Общие сведения. Базовые операции со списком
- Пример реализации линейного односвязного списка для обобщенного типа T
- Линейный двухсвязный список. Общие сведения. Базовые операции со списком
- Разработка шаблонного класса, реализующего стек в виде односвязного списка
⇑