Урок 151. Контейнерные классы

   ⁄ 

 Обновлено 24 Июн 2018  ⁄ 

 ⁄   4 

⁄   598

В реальной жизни мы постоянно используем контейнеры. Гречка с куриной грудкой в контейнере для еды, страницы в книге с обложкой и переплетом, вещи в тумбочке, в рюкзаке и т.д. Без этих контейнеров было бы крайне неудобно работать с объектами внутри. Представьте, что вы пытаетесь читать книгу без переплета и обложки, или пытаетесь есть гречку с грудкой, не используя контейнер для еды, миску, тарелку и прочего. Непорядок. Ценность контейнеров в том, что они помогают должным образом организовать и хранить объекты, которые в них помещают.

Аналогично, контейнерный класс (или класс-контейнер) в C++ — это класс, предназначенный для хранения и организации нескольких объектов определенного типа данных (пользовательских или фундаментальных). Существует много разных контейнерных классов, каждый из которых имеет свои преимущества, недостатки или ограничения в использовании. Безусловно, наиболее часто используемым контейнером в программировании является массив, который мы уже использовали во многих примерах. Хотя в C++ есть стандартные обычные массивы, большинство программистов используют контейнерные классы-массивы — std::array или std::vector из-за преимуществ, которые они предоставляют. В отличие от стандартных массивов, контейнерные классы-массивы имеют возможность динамического изменения своего размера, когда элементы добавляются или удаляются. Это не только делает их более удобными, чем обычные массивы, но и безопаснее.

Обычно функционал классов-контейнеров в C++ следующий:

  создание пустого контейнера (через конструктор);

  добавление нового объекта в контейнер;

  удаление объекта из контейнера;



  просмотр количества объектов, находящихся на данный момент в контейнере;

  очистка контейнера от всех объектов;

  доступ к сохраненным объектам;

  сортировка объектов/элементов (не всегда).

Иногда функционал контейнерных классов может быть меньшим, нежели мы указали выше. Например, контейнерные классы-массивы часто не имеют функционала добавления/удаления объектов, так как они и так медленные, и разработчик классов не хочет увеличивать нагрузку.

Типом отношений в классах-контейнерах является «член чего-то». Например, элементы массива «являются членами» (принадлежат) массива. Обратите внимание, мы здесь используем термин «член чего-то» не в смысле члена класса C++, а в общем.

Типы контейнеров в C++

Контейнерные классы обычно бывают двух разных типов. Контейнеры значения — это композиции, которые хранят копии объектов, которые они содержат (и, следовательно, ответственны за создание/уничтожение этих копий). Контейнеры ссылки – это агрегации, которые хранят указатели или ссылки на другие объекты (и, следовательно, не ответственны за создание/уничтожение этих объектов).

В отличие от реальной жизни, когда контейнеры могут хранить любые типы объектов, которые в них помещают, в C++ контейнеры обычно содержат только один тип данных. Например, если у вас целочисленный массив, то он может содержать только целочисленные значения. В отличие от некоторых других языков, C++ не позволяет смешивать разные типы данных внутри контейнеров. Если вам нужны контейнеры для хранения значений типов int и double, то вам придется написать два отдельных контейнера (или использовать шаблоны, о которых мы будем говорить в следующих главах). Несмотря на ограничения их использования, контейнеры чрезвычайно полезны, так как делают программирование проще, безопаснее и быстрее.

Контейнерный класс-массив

Сейчас мы напишем целочисленный класс-массив с нуля, реализуя функционал контейнеров в С++. Этот класс-массив будет типа контейнера значения, в котором будут храниться копии элементов, которые он содержит.

Сначала создадим файл ArrayInt.h:

Наш ArrayInt должен отслеживать два значения: непосредственно данные и свою длину (т.е. размер). Поскольку мы хотим, чтобы наш массив мог изменять свой размер, то нам нужно использовать динамическое выделение памяти, что означает, что мы будем использовать указатель для хранения данных.

Теперь нам нужно добавить конструкторы, чтобы иметь возможность создавать объекты класса ArrayInt. Мы добавим два конструктора: первый будет создавать пустой массив, второй — массив заданного размера.

Нам также потребуются функции, которые будут выполнять очистку ArrayInt. Во-первых, добавим деструктор, который будет просто освобождать любую динамически выделенную память. Во-вторых, напишем функцию erase(), которая будет выполнять очистку массива и сбрасывать его длину на 0.

Теперь перегрузим оператор индексации [], чтобы иметь доступ к элементам массива. Мы также должны выполнять проверку корректности передаваемого индекса, что лучше всего сделать с помощью стейтмента assert(). Также добавим функцию доступа для возврата длины массива:

Теперь у нас уже есть класс ArrayInt, который мы можем использовать. Мы можем выделить массив определенного размера и использовать оператор [] для извлечения или изменения значений элементов.

Тем не менее, есть еще несколько вещей, которые мы не можем выполнить с нашим ArrayInt. Это автоматическое изменение размера массива, добавление/удаление элементов, сортировка элементов.

Во-первых, давайте реализуем возможность массива изменять свой размер. Мы напишем две разные функции для этого. Первая функция, reallocate(), при изменении размера массива будет уничтожать все существующие элементы — это быстро. Вторая функция, resize(), при изменении размера массива будет сохранять все существующие элементы — это медленно.

Уф! Было непросто!

Функционал большинства контейнерных классов-массивов на этом прекращается. Однако, если вы хотите увидеть, как реализовать возможность добавления/удаления элементов, то мы вам покажем это. Следующих два алгоритма очень похожи на resize():

Вот наш контейнерный класс-массив ArrayInt целиком.

ArrayInt.h:

Теперь протестируем программу.

Результат:

50 1 2 3 4 15 6 7 35

Хотя написание контейнерных классов может быть несколько сложным, но хорошая новость в том, что вам их нужно написать только один раз. Как только контейнерный класс работает, вы можете его повторно использовать где-угодно без каких-либо дополнительных действий/усилий по части программирования.

Также стоит отметить, что, хотя наш контейнерный класс ArrayInt содержит фундаментальный тип данных (int), мы также могли бы легко использовать и пользовательский тип данных.



Последнее примечание: Если класс из стандартной библиотеки C++ полностью соответствует вашим потребностям, используйте его вместо написания своего контейнерного класса. Например, вместо ArrayInt лучше использовать std::vector<int>. Так как реализация std::vector<int> протестирована/проверена уже не одним годом использования, эффективна и отлично работает с другими классами из стандартной библиотеки C++. Но так как не всегда может быть возможным использовать классы из стандартной библиотеки C++, то вы уже знаете как создавать свои собственные контейнерные классы. Мы еще вернемся к теме контейнеров в C++ в следующих уроках.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (10 оценок, среднее: 5,00 из 5)
Загрузка...
Подписаться на обновления:

Комментариев: 4

  1. DenMark:

    Добрый день!
    Спасибо за ваши туториалы очень помогли при изучении С++. Скажите у вас будут туториалы по TCP/IP сокетам?

    1. Юрий:

      Привет. В ближайшем времени не планируется. В будущем — возможно.

  2. Shom:

    "Польза кувшина в его пустоте". Лао Цзы.

    1. Юрий:

      Эт точно 🙂

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

ПОДПИСЫВАЙТЕСЬ

НА КАНАЛ RAVESLI В TELEGRAM

@ravesli

ПОДПИСАТЬСЯ БЕСПЛАТНО