Приклад реалізації лінійного однозв’язного списку для узагальненого типу T. Список представлений в оперативній пам’яті
У прикладі демонструється розробка шаблонного класу, призначеного для реалізації однозв’язного списку який організовується в оперативній пам’яті. Клас реалізований в консольному додатку. Використовуючи буфер обміну можна об’єднати коди нижченаведених програмних елементів та отримати працездатний програмний код класу.
Перед вивченням даної теми рекомендується ознайомитись з наступною темою:
Зміст
- 1. Складові програми
- 2. Програмний код на мові C++
- 2.1. Базовий вузол. Структура Element
- 2.2. Клас List<T>
- 2.2.1. Додавання внутрішніх полів
- 2.2.2. Внутрішня функція Move(int)
- 2.2.3. Метод Copy(const List&). Зробити копію зі списку
- 2.2.4. Конструктор за замовчуванням List()
- 2.2.5. Конструктор копіювання List(const List&)
- 2.2.6. Оператор копіювання operator=(const List&)
- 2.2.7. Метод Add(T). Додати новий елемент в кінець списку
- 2.2.8. Метод Del(int). Видалити елемент зі списку в заданій позиції
- 2.2.9. Метод Del(). Видалити перший елемент зі списку
- 2.2.10. Очищення списку. Метод Clear()
- 2.2.11. Метод Count(). Повернути кількість елементів списку
- 2.2.12. Операторна функція operator[](int). Індексування списку як масиву
- 2.2.13. Метод Insert(T, int). Вставка елементу в задану позицію
- 2.2.14. Деструктор ~List()
- 2.2.15. Метод Print(). Виведення списку
- 3. Функція main(). Клієнтський код
- 4. Результат виконання програми
- Споріднені теми
Пошук на інших ресурсах:
1. Складові програми
У програмі реалізовані дві основні складові
- шаблонна структура Element<T>, що описує один елемент (вузол) списку;
- шаблонний клас List<T>, що реалізує список.
У класі List<T> оголошуються такі програмні елементи:
- покажчик begin, що вказує на початок списку;
- покажчик end, що вказує на кінець списку;
- лічильник count – задає кількість елементів у списку;
- внутрішній прихований (private) метод Move() – повертає елемент списку за заданим індексом;
- внутрішній прихований (private) метод Copy() – робить копію зі списку;
- конструктор без параметрів List() – створює пустий список;
- деструктор ~List() – виконує звільнення пам’яті, виділеної під елементи списку;
- конструктор копіювання List(const List&) – викликається при копіюванні списків в момент оголошення;
- оператор копіювання operator=(const List&) – викликається при копіюванні списків;
- метод Add(T) – додає новий елемент в кінець списку;
- метод Del(int) – видаляє елемент зі списку в заданій позиції;
- метод Del() – видалє перший елемент зі списку;
- метод Clear() – видаляє всі елементи зі списку;
- метод Count() – повертає кількість елементів списку;
- операторна функція operator[](int) – реалізує доступ до елементу списку за індексом [ ];
- метод Insert(T, int) – вставляє елемент списку в задану позицію;
- метод Print() – вивести поточний стан (значення) списку.
⇑
2. Програмний код на мові C++
2.1. Базовий вузол. Структура Element
Для представлення окремого елементу списку використовується узагальнена структура Element<T> яка отримує тип T в якості параметру. Ця структура містить поля:
- data – дані деякого узагальненого типу T. Типом T може бути будь-який клас чи структура або звичайний примітивний тип (int, double, char, …);
- next – покажчик на наступний елемент.
// Базовий вузол - елемент template <class T> struct Element { T data; // дані Element* next; // наступний елемент };
⇑
2.2. Клас List<T>
Після оголошення структури Element<T> створюється клас List<T>, який буде використовувати цю структуру. Початковий вигляд класу наступний
// Клас - однозв'язний список template <class T> class List { }
⇑
2.2.1. Додавання внутрішніх полів
У класі List<T> оголошуються 3 поля:
- begin – покажчик на голову списку;
- end – покажчик на кінець списку;
- count – поточна кількість елементів у списку.
За бажанням, можна зменшити кількість внутрішніх полів до одного, залишивши тільки поле begin або два поля begin і count. Однак, такі зміни призведуть до додавання додаткового програмного коду (перехід на кінець списку, визначення кількості елементів у списку тощо) при кожній обробці списку. Це уповільнить роботу програми.
Після додавання внутрішніх полів вигляд класу наступний
// Клас - однозв'язний список template <class T> class List { private: Element<T>* begin; // покажчик на голову списку Element<T>* end; // покажчик на кінець списку int count; // к-сть елементів у списку }
⇑
2.2.2. Внутрішня функція Move(int)
Внутрішня функція Move() використовується для пошуку (отримання) елементу в заданій позиції списку в інших загальнодоступних функціях класу.
// Клас - однозв'язний список template <class T> class List { private: ... // Внутрішня функція, яка перемотує на задану позицію, // використовується у декількох методах Element<T>* Move(int index) { if (count > 0) // якщо є елементи в списку { // Перемотати на позицію index Element<T>* t = begin; for (int i = 0; i < index; i++) t = t->next; return t; } return nullptr; } }
⇑
2.2.3. Метод Copy(const List&). Зробити копію зі списку
Важливим методом класу є метод Copy(), який здійснює копію зі списку. Цей метод викликається у конструкторі копіювання, операторі копіювання та може використовуватись в інших власно-розроблених функціях класу.
// Клас - однозв'язний список template <class T> class List { private: ... // Метод, який робить копію зі списку void Copy(const List& obj) { // 1. Звільнити пам'ять, виділену під попередній список Clear(); // 2. Копіювання списку obj => this Element<T>* t = obj.begin; for (int i = 0; i < obj.count; i++) { // Додати в кінець списку Add(t->data); t = t->next; } count = obj.count; } }
⇑
2.2.4. Конструктор за замовчуванням List()
У класі List<T> оголошується тільки один конструктор, який створює пустий список. За бажанням можна оголосити додаткові конструктори, які ініціалізуються деяким початковим значенням на основі вхідного масиву чи списку.
public: // Конструктор List() { // На початку список пустий begin = end = nullptr; count = 0; }
⇑
2.2.5. Конструктор копіювання List(const List&)
Оскільки в класі використовуються покажчики, то обов’язково потрібно реалізовувати конструктор копіювання, який викликається при оголошенні списку з його одночасною ініціалізацією.
public: ... // Конструктор копіювання List(const List& obj) { // Тут потрібно робити копію зі списку, // щоб уникнути недоліків побітового копіювання Copy(obj); }
⇑
2.2.6. Оператор копіювання operator=(const List&)
Оператор копіювання використовується при присвоєнні одного списку іншому та має таке саме призначення як конструктор копіювання.
public: ... // Оператор копіювання List<T>& operator=(const List& obj) { // Викликати внутрішню функцію Copy() Copy(obj); return *this; }
⇑
2.2.7. Метод Add(T). Додати новий елемент в кінець списку
Метод Add() додає новий елемент в кінець списку. В методі створюється нова комірка типу Element<T>. Ця комірка заповнюється даними та додається в кінець списку. При додаванні розглядається два випадки:
- додавання елементу до пустого списку;
- додавання елементу до непустого списку.
public: ... // Додати новий елемент в кінець списку void Add(T _data) { try { // 1. Створити новий елемент та заповнити його даними Element<T>* elem = new Element<T>; elem->data = _data; elem->next = nullptr; // останній елемент у списку // 2. Додати елемент // Визначити, чи елемент перший if (begin == nullptr) { // якщо елемент перший у списку begin = end = elem; } else { // якщо елемент не перший у списку, // то додати його в кінець списку. end->next = elem; end = elem; } // 3. Збільшити к-сть елементів у списку count++; } catch (bad_alloc e) { // Якщо пам'ять виділити не вдалось, то вивести системне повідомлення cout << e.what() << endl; } }
⇑
2.2.8. Метод Del(int). Видалити елемент зі списку в заданій позиції
Метод Del() отримує вхідним параметром позицію елементу, який потрібно видалити зі списку. Елемент видаляється при умові, що позиція видалення є коректною.
public: ... // Видалити елемент в позиції index, // позиція нумерується з 0 void Del(int index) { // 1. Перевірка, чи к-сть елементів рівна 0 if (count == 0) return; // 2. Перевірка, чи коректне значення index if ((index < 0) || (index >= count)) return; // 3. Видалення елементу. // Перевірка, чи видаляється перший елемент if (index == 0) { // 3.1. Якщо видаляється перший елемент Element<T>* t = begin; // запам'ятати адресу першого елементу begin = begin->next; // перейти на наступний елемент delete t; // звільнити перший елемент } else { // 3.2. Якщо видаляється не перший елемент. // 3.2.1. Перейти на елемент в позиції index-1 Element<T>* t = Move(index - 1); // 3.2.2. Запам'ятати елемент який видаляється Element<T>* t2 = t->next; // 3.2.3. Встановити наступний елемент для елементу в позиції index-1 t->next = t2->next; // працює коректно навіть для останнього елементу // 3.2.4. Видалити елемент на який вказує t2 delete t2; } // 4. Зменшити загальну к-сть елементів count--; cout << "Del(int)" << endl; }
⇑
2.2.9. Метод Del(). Видалити перший елемент зі списку
Модифікацією методу Del(int) є метод Del(), який видаляє перший елемент зі списку.
public: ... // Видалити перший елемент зі списку void Del() { // Викликати перевантажений метод Del(int) Del(0); }
⇑
2.2.10. Очищення списку. Метод Clear()
Очищення списку реалізоване в методі Clear(), який звертається до методу Del() до тих пір, поки початок списку begin не стане рівним nullptr.
public: ... // Метод, що видаляє всі елементи void Clear() { while (begin != nullptr) Del(); }
⇑
2.2.11. Метод Count(). Повернути кількість елементів списку
При необхідності отримати кількість елементів у списку, можна викликати метод Count(), який повертає значення внутрішньої змінної count.
public: ... // Повернути к-сть елементів у списку int Count() { return count; }
⇑
2.2.12. Операторна функція operator[](int). Індексування списку як масиву
Зручним способом звертання до елементу списку є звертання з допомогою операції індексування [ ] як у випадку з масивом. Щоб реалізувати це для класу List<T> потрібно перевантажити операторну функцію operator[](int). Щоб функція використовувалась у лівій та правій частині оператора присвоєння потрібно, щоб вона повертала посилання T&.
public: ... // Оператор індексування списку як масиву T& operator[](int index) { // Перевірка, чи коректне значення index if ((index < 0) || (index > count - 1)) { // Якщо не коректне, то викинути виключення throw out_of_range("Incorrect index."); } // Якщо значення index коректне, // то знайти потрібний елемент і повернути його значення Element<T>* t = Move(index); return t->data; }
⇑
2.2.13. Метод Insert(T, int). Вставка елементу в задану позицію
Для забезпечення зручності роботи зі списком додатково реалізовано метод Insert(), який вставляє елемент в задану позицію.
public: ... // Вставка елементу item в задану позицію index. // Позиція index нумерується з 0. void Insert(T item, int index) { // 1. Перевірка, чи коректне значення index, // тільки для випадку, якщо елементи є в списку if ((count != 0) && ((index < 0) || (index >= count))) return; // не робити ніякої вставки // 2. Спроба переходу до елементу в позиції index Element<T>* t = Move(index); // 3. Перевірка, чи існує список if (t == nullptr) { // 3.1. Якщо списку не існує, то додати елемент в кінець списку Add(item); } else { // 3.2. Якщо список існує, то реалізувати вставку try { // Спроба виділення пам'яті під нову комірку Element<T>* t2 = new Element<T>; t2->data = item; if (index == 0) // Якщо вставка в перший елемент { // Встановити початок списку на нову комірку t2->next = begin; begin = t2; } else { // Якщо вставка в другий, третій і т.д. елемент // Перейти до елементу index-1 t = Move(index - 1); // Вставка в другий, третій і т.д. елемент t2->next = t->next; t->next = t2; } // Збільшити к-сть елементів на 1 count++; } catch (bad_alloc e) { // Якщо пам'ять не вдалось виділити, то обробка виключної ситуації cout << e.what() << endl; } } }
⇑
2.2.14. Деструктор ~List()
У деструкторі викликається функція Clear(), яка здійснює поелементне звільнення пам’яті, виділеної для списку.
public: ... // Деструктор ~List() { // Очистити список Clear(); }
⇑
2.2.15. Метод Print(). Виведення списку
Функція Print() виводить список, що представлений в поточному екземплярі класу.
// Виведення списку - тестування void Print(string msg) { cout << msg << endl; if (count == 0) // якщо пустий список { cout << "List is empty." << endl; return; } // Обхід списку Element<T>* t = begin; while (t != nullptr) { cout << t->data << " "; t = t->next; } cout << endl; }
⇑
3. Функція main(). Клієнтський код
У функції main() здійснюється створення списку та тестування базових методів списку.
void main() { List<double> L; // Створити пустий список L.Print("L: "); // Пустий список L.Del(); // Додати до списку елементи L.Add(2.5); L.Add(3.8); L.Add(4.1); L.Add(3.3); L.Print("L+4: "); // Видалити один елемент L.Del(2); L.Print("L-1: "); // Змінити елемент у списку L[0] = 77.8; L.Print("L[0] = 77.8: "); // Вивести елементи - доступ за індексом cout << "L[1] = " << L[1] << endl; double z = L[2]; cout << "z = " << z << endl; cout << "count = " << L.Count() << endl; List<double> L2 = L; // Конструктор копіювання L2.Print("L2 = L: "); List<double> L3; L3.Print("L3: "); L2.Add(11.55); L2.Print("L2 + 11.55: "); L3 = L2; L3.Print("L3 + L2 + 11.55: "); L3.Clear(); L3.Print("L3.Clear: "); L3.Insert(225.8, 0); L3.Print("L3 + 225.8: "); L3.Insert(10.15, 0); L3.Print("L3 + 10.0: "); L3.Insert(77.77, 1); L3.Print("L3 + [77.77]: "); }
⇑
4. Результат виконання програми
L: List is empty. L+4: 2.5 3.8 4.1 3.3 L-1: 2.5 3.8 3.3 L[0] = 77.8: 77.8 3.8 3.3 L[1] = 3.8 z = 3.3 count = 3 L2 = L: 77.8 3.8 3.3 L3: List is empty. L2 + 11.55: 77.8 3.8 3.3 11.55 L3 + L2 + 11.55: 77.8 3.8 3.3 11.55 L3.Clear: List is empty. L3 + 225.8: 225.8 L3 + 10.0: 10.15 225.8 L3 + [77.77]: 10.15 77.77 225.8
⇑
Споріднені теми
- Лінійний однозв’язний список. Загальні відомості. Базові операції над списком
- Лінійний двозв’язний список. Загальні відомості. Базові операції над списком
- Лінійний двозв’язний список. Приклад шаблонного класу
- Розробка шаблонного класу, що реалізує стек у вигляді однозв’язного списку
⇑