Last active
October 2, 2023 09:47
-
-
Save NTG-TPL/b0b021dc1ed8cfc2e1886fc5c0f3a04b to your computer and use it in GitHub Desktop.
simple linked list C++ implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "single-linked-list.h" | |
#include <cstdalign> | |
void Test0() { | |
using namespace std; | |
{ | |
const SingleLinkedList<int> empty_int_list; | |
assert(empty_int_list.GetSize() == 0u); | |
assert(empty_int_list.IsEmpty()); | |
} | |
{ | |
const SingleLinkedList<string> empty_string_list; | |
assert(empty_string_list.GetSize() == 0u); | |
assert(empty_string_list.IsEmpty()); | |
} | |
} | |
// Эта функция проверяет работу класса SingleLinkedList | |
void Test() { | |
struct DeletionSpy { | |
~DeletionSpy() { | |
if (deletion_counter_ptr) { | |
++(*deletion_counter_ptr); | |
} | |
} | |
int* deletion_counter_ptr = nullptr; | |
}; | |
// Проверка PopFront | |
{ | |
SingleLinkedList<int> numbers{3, 14, 15, 92, 6}; | |
numbers.PopFront(); | |
assert((numbers == SingleLinkedList<int>{14, 15, 92, 6})); | |
SingleLinkedList<DeletionSpy> list; | |
list.PushFront(DeletionSpy{}); | |
int deletion_counter = 0; | |
list.begin()->deletion_counter_ptr = &deletion_counter; | |
assert(deletion_counter == 0); | |
list.PopFront(); | |
assert(deletion_counter == 1); | |
} | |
// Доступ к позиции, предшествующей begin | |
{ | |
SingleLinkedList<int> empty_list; | |
const auto& const_empty_list = empty_list; | |
assert(empty_list.before_begin() == empty_list.cbefore_begin()); | |
assert(++empty_list.before_begin() == empty_list.begin()); | |
assert(++empty_list.cbefore_begin() == const_empty_list.begin()); | |
SingleLinkedList<int> numbers{1, 2, 3, 4}; | |
const auto& const_numbers = numbers; | |
assert(numbers.before_begin() == numbers.cbefore_begin()); | |
assert(++numbers.before_begin() == numbers.begin()); | |
assert(++numbers.cbefore_begin() == const_numbers.begin()); | |
} | |
// Вставка элемента после указанной позиции | |
{ // Вставка в пустой список | |
{ | |
SingleLinkedList<int> lst; | |
const auto inserted_item_pos = lst.InsertAfter(lst.before_begin(), 123); | |
assert((lst == SingleLinkedList<int>{123})); | |
assert(inserted_item_pos == lst.begin()); | |
assert(*inserted_item_pos == 123); | |
} | |
// Вставка в непустой список | |
{ | |
SingleLinkedList<int> lst{1, 2, 3}; | |
auto inserted_item_pos = lst.InsertAfter(lst.before_begin(), 123); | |
assert(inserted_item_pos == lst.begin()); | |
assert(inserted_item_pos != lst.end()); | |
assert(*inserted_item_pos == 123); | |
assert((lst == SingleLinkedList<int>{123, 1, 2, 3})); | |
inserted_item_pos = lst.InsertAfter(lst.begin(), 555); | |
assert(++SingleLinkedList<int>::Iterator(lst.begin()) == inserted_item_pos); | |
assert(*inserted_item_pos == 555); | |
assert((lst == SingleLinkedList<int>{123, 555, 1, 2, 3})); | |
}; | |
} | |
// Вспомогательный класс, бросающий исключение после создания N-копии | |
struct ThrowOnCopy { | |
ThrowOnCopy() = default; | |
explicit ThrowOnCopy(int& copy_counter) noexcept | |
: countdown_ptr(©_counter) { | |
} | |
ThrowOnCopy(const ThrowOnCopy& other) | |
: countdown_ptr(other.countdown_ptr) // | |
{ | |
if (countdown_ptr) { | |
if (*countdown_ptr == 0) { | |
throw std::bad_alloc(); | |
} else { | |
--(*countdown_ptr); | |
} | |
} | |
} | |
// Присваивание элементов этого типа не требуется | |
ThrowOnCopy& operator=(const ThrowOnCopy& rhs) = delete; | |
// Адрес счётчика обратного отсчёта. Если не равен nullptr, то уменьшается при каждом копировании. | |
// Как только обнулится, конструктор копирования выбросит исключение | |
int* countdown_ptr = nullptr; | |
}; | |
// Проверка обеспечения строгой гарантии безопасности исключений | |
{ | |
bool exception_was_thrown = false; | |
for (int max_copy_counter = 10; max_copy_counter >= 0; --max_copy_counter) { | |
SingleLinkedList<ThrowOnCopy> list{ThrowOnCopy{}, ThrowOnCopy{}, ThrowOnCopy{}}; | |
try { | |
int copy_counter = max_copy_counter; | |
list.InsertAfter(list.cbegin(), ThrowOnCopy(copy_counter)); | |
assert(list.GetSize() == 4u); | |
} catch (const std::bad_alloc&) { | |
exception_was_thrown = true; | |
assert(list.GetSize() == 3u); | |
break; | |
} | |
} | |
assert(exception_was_thrown); | |
} | |
// Удаление элементов после указанной позиции | |
{ | |
{ | |
SingleLinkedList<int> lst{1, 2, 3, 4}; | |
const auto& const_lst = lst; | |
const auto item_after_erased = lst.EraseAfter(const_lst.cbefore_begin()); | |
assert((lst == SingleLinkedList<int>{2, 3, 4})); | |
assert(item_after_erased == lst.begin()); | |
} | |
{ | |
SingleLinkedList<int> lst{1, 2, 3, 4}; | |
const auto item_after_erased = lst.EraseAfter(lst.cbegin()); | |
assert((lst == SingleLinkedList<int>{1, 3, 4})); | |
assert(item_after_erased == (++lst.begin())); | |
} | |
{ | |
SingleLinkedList<int> lst{1, 2, 3, 4}; | |
const auto item_after_erased = lst.EraseAfter(++(++lst.cbegin())); | |
assert((lst == SingleLinkedList<int>{1, 2, 3})); | |
assert(item_after_erased == lst.end()); | |
} | |
{ | |
SingleLinkedList<DeletionSpy> list{DeletionSpy{}, DeletionSpy{}, DeletionSpy{}}; | |
auto after_begin = ++list.begin(); | |
int deletion_counter = 0; | |
after_begin->deletion_counter_ptr = &deletion_counter; | |
assert(deletion_counter == 0u); | |
list.EraseAfter(list.cbegin()); | |
assert(deletion_counter == 1u); | |
} | |
} | |
} | |
// Эта функция тестирует работу SingleLinkedList | |
void Test1() { | |
// Шпион, следящий за своим удалением | |
struct DeletionSpy { | |
DeletionSpy() = default; | |
explicit DeletionSpy(int& instance_counter) noexcept | |
: instance_counter_ptr_(&instance_counter) // | |
{ | |
OnAddInstance(); | |
} | |
DeletionSpy(const DeletionSpy& other) noexcept | |
: instance_counter_ptr_(other.instance_counter_ptr_) // | |
{ | |
OnAddInstance(); | |
} | |
DeletionSpy& operator=(const DeletionSpy& rhs) noexcept { | |
if (this != &rhs) { | |
auto rhs_copy(rhs); | |
std::swap(instance_counter_ptr_, rhs_copy.instance_counter_ptr_); | |
} | |
return *this; | |
} | |
~DeletionSpy() { | |
OnDeleteInstance(); | |
} | |
private: | |
void OnAddInstance() noexcept { | |
if (instance_counter_ptr_) { | |
++(*instance_counter_ptr_); | |
} | |
} | |
void OnDeleteInstance() noexcept { | |
if (instance_counter_ptr_) { | |
assert(*instance_counter_ptr_ != 0); | |
--(*instance_counter_ptr_); | |
} | |
} | |
int* instance_counter_ptr_ = nullptr; | |
}; | |
// Проверка вставки в начало | |
{ | |
SingleLinkedList<int> l; | |
assert(l.IsEmpty()); | |
assert(l.GetSize() == 0u); | |
l.PushFront(0); | |
l.PushFront(1); | |
assert(l.GetSize() == 2); | |
assert(!l.IsEmpty()); | |
l.Clear(); | |
assert(l.GetSize() == 0); | |
assert(l.IsEmpty()); | |
} | |
// Проверка фактического удаления элементов | |
{ | |
int item0_counter = 0; | |
int item1_counter = 0; | |
int item2_counter = 0; | |
{ | |
SingleLinkedList<DeletionSpy> list; | |
list.PushFront(DeletionSpy{item0_counter}); | |
list.PushFront(DeletionSpy{item1_counter}); | |
list.PushFront(DeletionSpy{item2_counter}); | |
assert(item0_counter == 1); | |
assert(item1_counter == 1); | |
assert(item2_counter == 1); | |
list.Clear(); | |
assert(item0_counter == 0); | |
assert(item1_counter == 0); | |
assert(item2_counter == 0); | |
list.PushFront(DeletionSpy{item0_counter}); | |
list.PushFront(DeletionSpy{item1_counter}); | |
list.PushFront(DeletionSpy{item2_counter}); | |
assert(item0_counter == 1); | |
assert(item1_counter == 1); | |
assert(item2_counter == 1); | |
} | |
assert(item0_counter == 0); | |
assert(item1_counter == 0); | |
assert(item2_counter == 0); | |
} | |
// Вспомогательный класс, бросающий исключение после создания N-копии | |
struct ThrowOnCopy { | |
ThrowOnCopy() = default; | |
explicit ThrowOnCopy(int& copy_counter) noexcept | |
: countdown_ptr(©_counter) { | |
} | |
ThrowOnCopy(const ThrowOnCopy& other) | |
: countdown_ptr(other.countdown_ptr) // | |
{ | |
if (countdown_ptr) { | |
if (*countdown_ptr == 0) { | |
throw std::bad_alloc(); | |
} else { | |
--(*countdown_ptr); | |
} | |
} | |
} | |
// Присваивание элементов этого типа не требуется | |
ThrowOnCopy& operator=(const ThrowOnCopy& rhs) = delete; | |
// Адрес счётчика обратного отсчёта. Если не равен nullptr, то уменьшается при каждом копировании. | |
// Как только обнулится, конструктор копирования выбросит исключение | |
int* countdown_ptr = nullptr; | |
}; | |
{ | |
bool exception_was_thrown = false; | |
// Последовательно уменьшаем счётчик копирований до нуля, пока не будет выброшено исключение | |
for (int max_copy_counter = 5; max_copy_counter >= 0; --max_copy_counter) { | |
// Создаём непустой список | |
SingleLinkedList<ThrowOnCopy> list; | |
list.PushFront(ThrowOnCopy{}); | |
try { | |
int copy_counter = max_copy_counter; | |
list.PushFront(ThrowOnCopy(copy_counter)); | |
// Если метод не выбросил исключение, список должен перейти в новое состояние | |
assert(list.GetSize() == 2); | |
} catch (const std::bad_alloc&) { | |
exception_was_thrown = true; | |
// После выбрасывания исключения состояние списка должно остаться прежним | |
assert(list.GetSize() == 1); | |
break; | |
} | |
} | |
assert(exception_was_thrown); | |
} | |
} | |
// Эта функция тестирует работу SingleLinkedList | |
void Test2() { | |
// Итерирование по пустому списку | |
{ | |
SingleLinkedList<int> list; | |
// Константная ссылка для доступа к константным версиям begin()/end() | |
const auto& const_list = list; | |
// Итераторы begin и end у пустого диапазона равны друг другу | |
assert(list.begin() == list.end()); | |
assert(const_list.begin() == const_list.end()); | |
assert(list.cbegin() == list.cend()); | |
assert(list.cbegin() == const_list.begin()); | |
assert(list.cend() == const_list.end()); | |
} | |
// Итерирование по непустому списку | |
{ | |
SingleLinkedList<int> list; | |
const auto& const_list = list; | |
list.PushFront(1); | |
assert(list.GetSize() == 1u); | |
assert(!list.IsEmpty()); | |
assert(const_list.begin() != const_list.end()); | |
assert(const_list.cbegin() != const_list.cend()); | |
assert(list.begin() != list.end()); | |
assert(const_list.begin() == const_list.cbegin()); | |
assert(*list.cbegin() == 1); | |
*list.begin() = -1; | |
assert(*list.cbegin() == -1); | |
const auto old_begin = list.cbegin(); | |
list.PushFront(2); | |
assert(list.GetSize() == 2); | |
const auto new_begin = list.cbegin(); | |
assert(new_begin != old_begin); | |
// Проверка прединкремента | |
{ | |
auto new_begin_copy(new_begin); | |
assert((++(new_begin_copy)) == old_begin); | |
} | |
// Проверка постинкремента | |
{ | |
auto new_begin_copy(new_begin); | |
assert(((new_begin_copy)++) == new_begin); | |
assert(new_begin_copy == old_begin); | |
} | |
// Итератор, указывающий на позицию после последнего элемента, равен итератору end() | |
{ | |
auto old_begin_copy(old_begin); | |
assert((++old_begin_copy) == list.end()); | |
} | |
} | |
// Преобразование итераторов | |
{ | |
SingleLinkedList<int> list; | |
list.PushFront(1); | |
// Конструирование ConstIterator из Iterator | |
SingleLinkedList<int>::ConstIterator const_it(list.begin()); | |
assert(const_it == list.cbegin()); | |
assert(*const_it == *list.cbegin()); | |
SingleLinkedList<int>::ConstIterator const_it1; | |
// Присваивание ConstIterator'у значения Iterator | |
const_it1 = list.begin(); | |
assert(const_it1 == const_it); | |
} | |
// Проверка оператора -> | |
{ | |
using namespace std; | |
SingleLinkedList<std::string> string_list; | |
string_list.PushFront("one"s); | |
assert(string_list.cbegin()->length() == 3u); | |
string_list.begin()->push_back('!'); | |
assert(*string_list.begin() == "one!"s); | |
} | |
} | |
void Test3() { | |
// Проверка списков на равенство и неравенство | |
{ | |
SingleLinkedList<int> list_1; | |
list_1.PushFront(1); | |
list_1.PushFront(2); | |
SingleLinkedList<int> list_2; | |
list_2.PushFront(1); | |
list_2.PushFront(2); | |
list_2.PushFront(3); | |
SingleLinkedList<int> list_1_copy; | |
list_1_copy.PushFront(1); | |
list_1_copy.PushFront(2); | |
SingleLinkedList<int> empty_list; | |
SingleLinkedList<int> another_empty_list; | |
// Список равен самому себе | |
assert(list_1 == list_1); | |
assert(empty_list == empty_list); | |
// Списки с одинаковым содержимым равны, а с разным - не равны | |
assert(list_1 == list_1_copy); | |
assert(list_1 != list_2); | |
assert(list_2 != list_1); | |
assert(empty_list == another_empty_list); | |
} | |
// Обмен содержимого списков | |
{ | |
SingleLinkedList<int> first; | |
first.PushFront(1); | |
first.PushFront(2); | |
SingleLinkedList<int> second; | |
second.PushFront(10); | |
second.PushFront(11); | |
second.PushFront(15); | |
const auto old_first_begin = first.begin(); | |
const auto old_second_begin = second.begin(); | |
const auto old_first_size = first.GetSize(); | |
const auto old_second_size = second.GetSize(); | |
first.swap(second); | |
assert(second.begin() == old_first_begin); | |
assert(first.begin() == old_second_begin); | |
assert(second.GetSize() == old_first_size); | |
assert(first.GetSize() == old_second_size); | |
// Обмен при помощи функции swap | |
{ | |
using std::swap; | |
// В отсутствие пользовательской перегрузки будет вызвана функция std::swap, которая | |
// выполнит обмен через создание временной копии | |
swap(first, second); | |
// Убеждаемся, что используется не std::swap, а пользовательская перегрузка | |
// Если бы обмен был выполнен с созданием временной копии, | |
// то итератор first.begin() не будет равен ранее сохранённому значению, | |
// так как копия будет хранить свои узлы по иным адресам | |
assert(first.begin() == old_first_begin); | |
assert(second.begin() == old_second_begin); | |
assert(first.GetSize() == old_first_size); | |
assert(second.GetSize() == old_second_size); | |
} | |
} | |
// Инициализация списка при помощи std::initializer_list | |
{ | |
SingleLinkedList<int> list{1, 2, 3, 4, 5}; | |
assert(list.GetSize() == 5); | |
assert(!list.IsEmpty()); | |
assert(std::equal(list.begin(), list.end(), std::begin({1, 2, 3, 4, 5}))); | |
} | |
// Лексикографическое сравнение списков | |
{ | |
using IntList = SingleLinkedList<int>; | |
assert((IntList{1, 2, 3} < IntList{1, 2, 3, 1})); | |
assert((IntList{1, 2, 3} <= IntList{1, 2, 3})); | |
assert((IntList{1, 2, 4} > IntList{1, 2, 3})); | |
assert((IntList{1, 2, 3} >= IntList{1, 2, 3})); | |
} | |
// Копирование списков | |
{ | |
const SingleLinkedList<int> empty_list{}; | |
// Копирование пустого списка | |
{ | |
auto list_copy(empty_list); | |
assert(list_copy.IsEmpty()); | |
} | |
SingleLinkedList<int> non_empty_list{1, 2, 3, 4}; | |
// Копирование непустого списка | |
{ | |
auto list_copy(non_empty_list); | |
assert(non_empty_list.begin() != list_copy.begin()); | |
assert(list_copy == non_empty_list); | |
} | |
} | |
// Присваивание списков | |
{ | |
const SingleLinkedList<int> source_list{1, 2, 3, 4}; | |
SingleLinkedList<int> receiver{5, 4, 3, 2, 1}; | |
receiver = source_list; | |
assert(receiver.begin() != source_list.begin()); | |
assert(receiver == source_list); | |
} | |
// Вспомогательный класс, бросающий исключение после создания N-копии | |
struct ThrowOnCopy { | |
ThrowOnCopy() = default; | |
explicit ThrowOnCopy(int& copy_counter) noexcept | |
: countdown_ptr(©_counter) { | |
} | |
ThrowOnCopy(const ThrowOnCopy& other) | |
: countdown_ptr(other.countdown_ptr) // | |
{ | |
if (countdown_ptr) { | |
if (*countdown_ptr == 0) { | |
throw std::bad_alloc(); | |
} else { | |
--(*countdown_ptr); | |
} | |
} | |
} | |
// Присваивание элементов этого типа не требуется | |
ThrowOnCopy& operator=(const ThrowOnCopy& rhs) = delete; | |
// Адрес счётчика обратного отсчёта. Если не равен nullptr, то уменьшается при каждом копировании. | |
// Как только обнулится, конструктор копирования выбросит исключение | |
int* countdown_ptr = nullptr; | |
}; | |
// Безопасное присваивание списков | |
{ | |
SingleLinkedList<ThrowOnCopy> src_list; | |
src_list.PushFront(ThrowOnCopy{}); | |
src_list.PushFront(ThrowOnCopy{}); | |
auto thrower = src_list.begin(); | |
src_list.PushFront(ThrowOnCopy{}); | |
int copy_counter = 0; // при первом же копировании будет выброшего исключение | |
thrower->countdown_ptr = ©_counter; | |
SingleLinkedList<ThrowOnCopy> dst_list; | |
dst_list.PushFront(ThrowOnCopy{}); | |
int dst_counter = 10; | |
dst_list.begin()->countdown_ptr = &dst_counter; | |
dst_list.PushFront(ThrowOnCopy{}); | |
try { | |
dst_list = src_list; | |
// Ожидается исключение при присваивании | |
assert(false); | |
} catch (const std::bad_alloc&) { | |
// Проверяем, что состояние списка-приёмника не изменилось | |
// при выбрасывании исключений | |
assert(dst_list.GetSize() == 2); | |
auto it = dst_list.begin(); | |
assert(it != dst_list.end()); | |
assert(it->countdown_ptr == nullptr); | |
++it; | |
assert(it != dst_list.end()); | |
assert(it->countdown_ptr == &dst_counter); | |
assert(dst_counter == 10); | |
} catch (...) { | |
// Других типов исключений не ожидается | |
assert(false); | |
} | |
} | |
} | |
// Эта функция проверяет работу класса SingleLinkedList | |
void Test4() { | |
struct DeletionSpy { | |
~DeletionSpy() { | |
if (deletion_counter_ptr) { | |
++(*deletion_counter_ptr); | |
} | |
} | |
int* deletion_counter_ptr = nullptr; | |
}; | |
// Проверка PopFront | |
{ | |
SingleLinkedList<int> numbers{3, 14, 15, 92, 6}; | |
numbers.PopFront(); | |
assert((numbers == SingleLinkedList<int>{14, 15, 92, 6})); | |
SingleLinkedList<DeletionSpy> list; | |
list.PushFront(DeletionSpy{}); | |
int deletion_counter = 0; | |
list.begin()->deletion_counter_ptr = &deletion_counter; | |
assert(deletion_counter == 0); | |
list.PopFront(); | |
assert(deletion_counter == 1); | |
} | |
// Доступ к позиции, предшествующей begin | |
{ | |
SingleLinkedList<int> empty_list; | |
const auto& const_empty_list = empty_list; | |
assert(empty_list.before_begin() == empty_list.cbefore_begin()); | |
assert(++empty_list.before_begin() == empty_list.begin()); | |
assert(++empty_list.cbefore_begin() == const_empty_list.begin()); | |
SingleLinkedList<int> numbers{1, 2, 3, 4}; | |
const auto& const_numbers = numbers; | |
assert(numbers.before_begin() == numbers.cbefore_begin()); | |
assert(++numbers.before_begin() == numbers.begin()); | |
assert(++numbers.cbefore_begin() == const_numbers.begin()); | |
} | |
// Вставка элемента после указанной позиции | |
{ // Вставка в пустой список | |
{ | |
SingleLinkedList<int> lst; | |
const auto inserted_item_pos = lst.InsertAfter(lst.before_begin(), 123); | |
assert((lst == SingleLinkedList<int>{123})); | |
assert(inserted_item_pos == lst.begin()); | |
assert(*inserted_item_pos == 123); | |
} | |
// Вставка в непустой список | |
{ | |
SingleLinkedList<int> lst{1, 2, 3}; | |
auto inserted_item_pos = lst.InsertAfter(lst.before_begin(), 123); | |
assert(inserted_item_pos == lst.begin()); | |
assert(inserted_item_pos != lst.end()); | |
assert(*inserted_item_pos == 123); | |
assert((lst == SingleLinkedList<int>{123, 1, 2, 3})); | |
inserted_item_pos = lst.InsertAfter(lst.begin(), 555); | |
assert(++SingleLinkedList<int>::Iterator(lst.begin()) == inserted_item_pos); | |
assert(*inserted_item_pos == 555); | |
assert((lst == SingleLinkedList<int>{123, 555, 1, 2, 3})); | |
}; | |
} | |
// Вспомогательный класс, бросающий исключение после создания N-копии | |
struct ThrowOnCopy { | |
ThrowOnCopy() = default; | |
explicit ThrowOnCopy(int& copy_counter) noexcept | |
: countdown_ptr(©_counter) { | |
} | |
ThrowOnCopy(const ThrowOnCopy& other) | |
: countdown_ptr(other.countdown_ptr) // | |
{ | |
if (countdown_ptr) { | |
if (*countdown_ptr == 0) { | |
throw std::bad_alloc(); | |
} else { | |
--(*countdown_ptr); | |
} | |
} | |
} | |
// Присваивание элементов этого типа не требуется | |
ThrowOnCopy& operator=(const ThrowOnCopy& rhs) = delete; | |
// Адрес счётчика обратного отсчёта. Если не равен nullptr, то уменьшается при каждом копировании. | |
// Как только обнулится, конструктор копирования выбросит исключение | |
int* countdown_ptr = nullptr; | |
}; | |
// Проверка обеспечения строгой гарантии безопасности исключений | |
{ | |
bool exception_was_thrown = false; | |
for (int max_copy_counter = 10; max_copy_counter >= 0; --max_copy_counter) { | |
SingleLinkedList<ThrowOnCopy> list{ThrowOnCopy{}, ThrowOnCopy{}, ThrowOnCopy{}}; | |
try { | |
int copy_counter = max_copy_counter; | |
list.InsertAfter(list.cbegin(), ThrowOnCopy(copy_counter)); | |
assert(list.GetSize() == 4u); | |
} catch (const std::bad_alloc&) { | |
exception_was_thrown = true; | |
assert(list.GetSize() == 3u); | |
break; | |
} | |
} | |
assert(exception_was_thrown); | |
} | |
// Удаление элементов после указанной позиции | |
{ | |
{ | |
SingleLinkedList<int> lst{1, 2, 3, 4}; | |
const auto& const_lst = lst; | |
const auto item_after_erased = lst.EraseAfter(const_lst.cbefore_begin()); | |
assert((lst == SingleLinkedList<int>{2, 3, 4})); | |
assert(item_after_erased == lst.begin()); | |
} | |
{ | |
SingleLinkedList<int> lst{1, 2, 3, 4}; | |
const auto item_after_erased = lst.EraseAfter(lst.cbegin()); | |
assert((lst == SingleLinkedList<int>{1, 3, 4})); | |
assert(item_after_erased == (++lst.begin())); | |
} | |
{ | |
SingleLinkedList<int> lst{1, 2, 3, 4}; | |
const auto item_after_erased = lst.EraseAfter(++(++lst.cbegin())); | |
assert((lst == SingleLinkedList<int>{1, 2, 3})); | |
assert(item_after_erased == lst.end()); | |
} | |
{ | |
SingleLinkedList<DeletionSpy> list{DeletionSpy{}, DeletionSpy{}, DeletionSpy{}}; | |
auto after_begin = ++list.begin(); | |
int deletion_counter = 0; | |
after_begin->deletion_counter_ptr = &deletion_counter; | |
assert(deletion_counter == 0u); | |
list.EraseAfter(list.cbegin()); | |
assert(deletion_counter == 1u); | |
} | |
} | |
} | |
void TestRun(){ | |
Test(); | |
Test0(); | |
Test1(); | |
Test2(); | |
Test3(); | |
Test4(); | |
} | |
int main() { | |
TestRun(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef SINGLELINKEDLIST_H | |
#define SINGLELINKEDLIST_H | |
#include <cassert> | |
#include <cstddef> | |
#include <string> | |
#include <utility> | |
#include <iostream> | |
using namespace std; | |
template <typename Type> | |
class SingleLinkedList { | |
// Узел списка | |
struct Node { | |
Node() = default; | |
Node(const Type& val, Node* next) | |
: value(val) | |
, next_node(next) { | |
} | |
Type value; | |
Node* next_node = nullptr; | |
}; | |
template <typename ValueType> | |
class BasicIterator { | |
public: | |
using iterator_category = std::forward_iterator_tag; | |
using value_type = Type; | |
using difference_type = std::ptrdiff_t; | |
using pointer = ValueType*; | |
using reference = ValueType&; | |
BasicIterator() = default; | |
BasicIterator(const BasicIterator<Type>& other) noexcept : node_(other.node_) {} | |
BasicIterator& operator=(const BasicIterator& other) = default; | |
[[nodiscard]] bool operator==(const BasicIterator<const Type>& rhs) const noexcept { | |
return this->node_ == rhs.node_; | |
} | |
[[nodiscard]] bool operator!=(const BasicIterator<const Type>& rhs) const noexcept { | |
return !(*this == rhs); | |
} | |
[[nodiscard]] bool operator==(const BasicIterator<Type>& rhs) const noexcept { | |
return this->node_ == rhs.node_; | |
} | |
[[nodiscard]] bool operator!=(const BasicIterator<Type>& rhs) const noexcept { | |
return !(*this == rhs); | |
} | |
BasicIterator& operator++() noexcept { | |
assert(this->node_ != nullptr); | |
this->node_ = this->node_->next_node; | |
return *this; | |
} | |
BasicIterator operator++(int) noexcept { | |
auto old_value(*this); | |
++(*this); | |
return old_value; | |
} | |
[[nodiscard]] reference operator*() const noexcept { | |
assert(this->node_ != nullptr); | |
return node_->value; | |
} | |
[[nodiscard]] pointer operator->() const noexcept { | |
assert(this->node_ != nullptr); | |
return &node_->value ; | |
} | |
private: | |
// Разрешаем SingleLinkedList обращаться к приватной области | |
friend class SingleLinkedList; | |
explicit BasicIterator(Node* node): node_(node) {} | |
Node* node_ = nullptr; | |
}; | |
public: | |
void swap(SingleLinkedList& other) noexcept { | |
std::swap(this->head_.next_node , other.head_.next_node); | |
std::swap(this->size_, other.size_); | |
} | |
SingleLinkedList() {}; | |
SingleLinkedList(std::initializer_list<Type> values) { | |
Assign(values.begin(), values.end()); | |
} | |
SingleLinkedList(const SingleLinkedList& other) { | |
Assign(other.begin(), other.end()); | |
} | |
SingleLinkedList& operator=(const SingleLinkedList& rhs) { | |
auto rhs_copy(rhs); | |
this->swap(rhs_copy); | |
return *this; | |
} | |
void PushFront(const Type& value){ | |
head_.next_node = new Node(value, head_.next_node); | |
++size_; | |
} | |
void Clear(){ | |
while(!this->IsEmpty()){ | |
auto temporary = head_.next_node->next_node; | |
delete head_.next_node; | |
head_.next_node = temporary; | |
--size_; | |
} | |
} | |
// Возвращает количество элементов в списке за время O(1) | |
[[nodiscard]] size_t GetSize() const noexcept { | |
// Заглушка. Реализуйте метод самостоятельно | |
return size_; | |
} | |
// Сообщает, пустой ли список за время O(1) | |
[[nodiscard]] bool IsEmpty() const noexcept { | |
// Заглушка. Реализуйте метод самостоятельно | |
return size_ == 0; | |
} | |
~SingleLinkedList(){ | |
Clear(); | |
} | |
using Iterator = BasicIterator<Type>; | |
using ConstIterator = BasicIterator<const Type>; | |
[[nodiscard]] Iterator begin() noexcept { | |
return Iterator{head_.next_node}; | |
} | |
[[nodiscard]] Iterator end() noexcept { | |
return Iterator(nullptr); | |
} | |
// Константные версии begin/end для обхода списка без возможности модификации его элементов | |
[[nodiscard]] ConstIterator begin() const noexcept { | |
return ConstIterator{head_.next_node}; | |
} | |
[[nodiscard]] ConstIterator end() const noexcept { | |
return ConstIterator(nullptr); | |
} | |
// Методы для удобного получения константных итераторов у неконстантного контейнера | |
[[nodiscard]] ConstIterator cbegin() const noexcept { | |
return ConstIterator{head_.next_node}; | |
} | |
[[nodiscard]] ConstIterator cend() const noexcept { | |
return ConstIterator(nullptr); | |
} | |
[[nodiscard]] Iterator before_begin() noexcept{ | |
return Iterator{&head_}; | |
} | |
[[nodiscard]] ConstIterator cbefore_begin() const noexcept{ | |
return ConstIterator{const_cast<Node *>(&head_)}; | |
} | |
[[nodiscard]] ConstIterator before_begin() const noexcept{ | |
return ConstIterator{&head_}; | |
} | |
Iterator InsertAfter(ConstIterator pos, const Type& value) { | |
assert(pos.node_ != nullptr); // проверка на pos.node_->next_node != nullptr не нужна | |
auto next_copy(pos.node_->next_node); | |
pos.node_->next_node = new Node(value, next_copy); | |
pos.node_->next_node->next_node = next_copy; | |
++size_; | |
return Iterator{ pos.node_->next_node}; | |
} | |
void PopFront() noexcept { | |
if(!IsEmpty()){ | |
auto next_copy(head_.next_node->next_node); | |
delete head_.next_node; | |
head_.next_node = next_copy; | |
--size_; | |
} | |
} | |
Iterator EraseAfter(ConstIterator pos) noexcept { | |
assert(pos.node_ != nullptr && pos.node_->next_node != nullptr); | |
if(!IsEmpty()){ // понимаю , что this-> не обязателен. | |
auto next_copy(pos.node_->next_node->next_node); | |
delete pos.node_->next_node; | |
pos.node_->next_node = next_copy; | |
--size_; | |
} | |
return Iterator{pos.node_->next_node}; | |
} | |
private: | |
template <typename InputIterator> | |
void Assign(InputIterator from, InputIterator to) { | |
SingleLinkedList<Type> tmp; | |
Node** node_ptr = &tmp.head_.next_node; | |
while (from != to) { | |
// Ожидается, что текущий указатель - нулевой | |
assert(*node_ptr == nullptr); | |
*node_ptr = new Node(*from, nullptr); | |
++tmp.size_; | |
node_ptr = &((*node_ptr)->next_node); | |
++from; | |
} | |
swap(tmp); | |
} | |
Node head_; | |
size_t size_ = 0; | |
}; | |
template <typename Type> | |
void swap(SingleLinkedList<Type>& lhs, SingleLinkedList<Type>& rhs) noexcept { | |
lhs.swap(rhs); | |
} | |
template <typename Type> | |
bool operator==(const SingleLinkedList<Type>& lhs, const SingleLinkedList<Type>& rhs) { | |
if(&lhs == &rhs){ | |
return true; | |
} | |
if(lhs.GetSize() != rhs.GetSize()){ | |
return false; | |
} | |
bool equal = std::equal(lhs.begin(), lhs.end(), rhs.begin()); | |
return equal; | |
} | |
template <typename Type> | |
bool operator!=(const SingleLinkedList<Type>& lhs, const SingleLinkedList<Type>& rhs) { | |
return !(lhs == rhs); | |
} | |
template <typename Type> | |
bool operator<(const SingleLinkedList<Type>& lhs, const SingleLinkedList<Type>& rhs) { | |
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); | |
} | |
template <typename Type> | |
bool operator<=(const SingleLinkedList<Type>& lhs, const SingleLinkedList<Type>& rhs) { | |
return !(lhs > rhs); | |
} | |
template <typename Type> | |
bool operator>(const SingleLinkedList<Type>& lhs, const SingleLinkedList<Type>& rhs) { | |
return rhs < lhs; | |
} | |
template <typename Type> | |
bool operator>=(const SingleLinkedList<Type>& lhs, const SingleLinkedList<Type>& rhs) { | |
return !(lhs < rhs); | |
} | |
#endif // SINGLELINKEDLIST_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment