STL. Vector. Знакомство с вектором

Векторы, используемые в С++ в качестве массивов, в очень старых компиляторах не поддерживаются. Поэтому чтобы изучать те векторы, которые используют в С++, нужно использовать компиляторы поддерживающие их. Вы легко можете понять, поддерживает или нет ваш компилятор векторы С++, просто попробовав выполнить следующую программу:

Строчку using namespace std; в старых компиляторах написать нельзя, потому что из-за неё код не будет компилироваться, но во всех распространённых ныне компиляторах она пишется почти всеми новичками.
  • В отличие от геометрического вектора у вектора из языка С++ другое назначение — это динамический массив.
Динамический массив — это такой массив, который может и расширать и сужать свою фактическую вместимость. Обычный массив всегда имеет фиксированную вместимость, изменить которую по ходу программы нельзя. Из-за того что по ходу программы вместимость обычного массива изменить нельзя, мы даже не можем использовать вот такой код:
В некоторых компиляторах показанный код будет компилироваться, но это благодаря расширениям компиляторов, но не по правилам языка. Да и изменить вместимость в ходе работы программы всё равно не получится:


Обычному массиву нужно объяснять вместимость константой времени компиляции. Если значение константы определяется во время выполнения программы, то нельзя:


И много всяких ограничений мы можем встретить с обычным массивом ещё. Эта тема всё-таки о векторе, поэтому описание обычного массива прекращаю. Возвращаюсь к вектору. В отличие от обычного массива объекты вектора умеют влиять на память: расширять при необходимости или наоборот: сужать. Но как и с обычным массивом мы можем ненароком нарушить существующие границы. Всё-таки вектор прежде всего массив и актуальный размер его ограничивается, и если мы выйдем за допустимую норму до того момента как векторв будет расширен, то мы нарушим границу, котоую нарушать нельзя.
  • Векторы являются частью STL и относятся к последовательным контейнерам.
  • Последовательные контейнеры — это упорядоченные коллекции, в которых каждый элемент занимает определенную позицию.
Поскольку вектор прежде всего массив (массив с расширенной функциональностью), то с объектами-векторами можно работать как с обычным массивом. Вектор всегда может вычислить свою действительную вместимость с помощью функции size(). В разные моменты времени вместимость может изменяться (о чём говорит определение вектора как динамического массива) и всегда можно узнать последнюю актуальную.
Чтобы создать вектор с определённо выбранной вместимостью, чтобы было похоже на создание массива, когда мы знаем, сколько элементов нам сразу надо, можно использовать вот такой код:


Чтобы было наглядно очевидное сходство векторов и обычных массивов, используем обе формы масивов в одном коде:


В отличие от обычных массивов векторы обладают свойством дополнительной функциональности. Поскольку векторы — это объекты класса, а в том классе описаны специальные функции для объектов, то мы можем использовать те описанные функции для всех объектов-векторов.
Часть основных функций объектов-векторов:

  • push_back(element) — добавить элемент в конец объект-вектора
  • pop_back(element) — удалить последний элемент объект-вектора
  • sizeof() — Число элементов в векторе
  • capacity() — Вместимость вектора
  • insert(…) — Существует три варианта вставки в какую-либо область в объект-векторе. Первый аргумент — позиция вставки, заданная итератором, остальные указывают на контейнер, или количество и контейнер, или пару итераторов, указывающих от какой и до какой позиции из другого контейнера взять данные.
  • erase(iterator или iterator от, и до) — Удаляет элемент или последовательность элементов из объект-вектора.
  • begin() — Возвращает итератор, указывающий на начало коллекции.
  • end() — Возвращает итератор, указывающий на конец коллекции. При этом он указывает не самый последний элемент, а на "воображаемый элемент за последним.
  • at(index) — Метод доступа к элементам коллекции, в отличие от операции [ ] проверяет выход из-за границ коллекции, и в случае чего генерит исключение.
  • clear() — Удаляет все элементы коллекции, при этом если в объект-векторе содержатся объекты классов, вызывает их деструкторы. А если же содержатся указатели на объекты, то может произойти утечка памяти (memory leaks=), так как delete, если будет нужен, ничто и никто не зазовёт.
  • reserve(число) — Резервирует определённый объём памяти под указанную вместимость
  • resize(число) — Изменяет актуальный размер вектора под указанное число элементов.
  • shrink_to_fit() — Выравнивает фактический размер и зарезервированный размер памти, путём усечения резервных ячеек.(С++11 и новее)
Моё знакомство с векторами началось с методов push_back и pop_back. Моя первая ошибка демонстрируется в листинге #b1


Когда я писал такой код, то не совсем понимал метод push_back, поэтому на экран получил нули и стал искать в чём дело. Возможно кто-то из читателей мог как и я невнимательно прочитать, что это за метод такой push_back. Это метод для добавления элементов в конец коллекции. Элементы приставляются в хвост массива, удлинняя сам массив. Другими словами, эта моя программа увеличила размер массива путем добавления новых к нему элементов, и чтобы увидеть весь массив, нужно этот массив выводить с правильным размером:


Использовать метод size всяко удобнее, чем указывать размер цифрами.
Одним из первых заданий, с которым, как я понимаю, может столкнуться обучающийся на стадии изучения векторов — это копирование значений обычного массива в объект-вектор. Можно использовать разные способы, такие как поэлементное копирование с помощью цикла (как копирование обычных массивов), копирование с помощью memcpy (такое копирование не приветствуется, лучше memmove) и можно использовать поэлементное заполнение, этот код я сейчас покажу, а также тучу всяких способов, которые я пока не покажу.


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

  • Итератор — это специальный элемент, подобный указателю, предназначенный для навигации по элементам коллекции.
  • Итераторы — это не указатели как таковые, а объекты обобщённых классов, выполняющие роль указательных переменных, подходящих обобщённому классу.
Понятие итератора очень важно в начале изучения STL. Первое, что вы должны принять, это то, что итераторы дают нам доступ к эллементам коллекций (коллекции — это структуры данных, умеющие хранить множество элементов, кроме массива существуют и другие структуры). Любой контейнер стандартной библиотеки шаблонов (а вектор — это контейнер такой библиотеки) всегда содержит методы begin() и end(), по которым всегда можно отследить начало расположения объекта-вектора в памяти и конец. Эти методы возвращают соответствующие указательные переменные на адреса. И эти адреса можно использовать, используя итераторы в качестве бегунков.

Если у вектора известны границы (во избежание выхода в память в неразрешённую зону вектора), можно использовать специальные функции, которые избавят нас от необходимости своими силами гонять итераторы по коллекции:


Мне очень удобно называть итераторы бегунками. Но это моё личное название, в технической терминологии такого нет.
Со времён принятия стандарта С++11 стало возможно инициализировать вектор списковой инициализацией. Это такая инициализация, которая напоминает инициализацию обычных массивов с помощью фигурных скобок:


Объект-вектор может по мере необходимости при добавлении очередного в себя элемента расширять себе используемую память автоматически, но никогда своими силами не сужает излишки, появление которых возможно по ходу работы программы после удалений. Удаляемые элементы оставляют после себя использованную собой память в резерве объекта. Связано это с тем, что очистка памяти наряду с выделением — операции дорогостоящие, т. е. времязатратные, поэтому в угоду эффективности остаётся резерв ячейки памяти за объектом. Так не придётся перевыделять и переуничтожать многократно, что благоприятно сказывается на скорости работы программы. Но при желании мы своими силами можем насильно подчистить память, оказавшуюся в резерве объекта-вектора.
Из-за динамичности вектора следует различать понятия ёмкости и размера вектора. Ёмкость вектора предполагает собой общую вместимость элементов, а размер вектора представляет собой количество элементов, расположившихся внутри вектора. (есть ещё второе понятие размера, размер типа vector, а не объекта вектора, не cпутайте, речь дальше о размере объекта вектора)

  • size() — функция объект-вектора, позволяющая узнать количество элементов, расположившехся внутри объекта-вектора.
  • capacity() — функция объект-вектора, позволяющая узнать, на сколько элементов заточен объём объект-вектора.
  • capacity() отображает общее число ячеек, которые используются и забронированы вектором, но не число ячеек, которые могут быть подвержены какому-нибудь влияюнию модификаций. Если мы выйдем за разрешённые границы актуального размера, то программа будет вести себя непредсказуемо.


Если мы бронируем ячейки под объекты-вектора, но используем их до того момента, как массив уширился, то мы используем сырую память, из-за которой объекты, так сказать, недоготавливаются, если у объектов есть описанные конструкторы. Но эта внутрення кухня вектора не предполагается сейчас для описания совсем новичку. Не будем углубляться. Просто пока что достаточно знать, что выход за актуальный размер объект-вектора и попытка повлиять на ячейку ведёт к непрогнозируемости работы программы.
Для того, чтобы расширить объект-вектор вручную, нужно использовать метод resize(). Метод resize() легко расширит фактическую вместимость вектора, но не сможет убрать излишки резерва.


Чтобы убрать излишки резерва, нужно использовать метод shrink_to_fit(), если ваш компилятор поддерживает С++11:


Поскольку на день написания статьи не все компиляторы поддерживают С++11, то для исполнения такой цели используют трюк, называемый Swap Trick (трюк обмена).


Написанию части этой статьи сильно помогла статья из блога Алёны.
  • Для предотвращения лишних операций перераспределения памяти рекомендуется использовать reserve().
К особенностям работы с векторами можно добавить и то, что скорость работы векторов уступает скорости работе массивов, и то, что ёмкость вектора больше, чем объём занимаемой памяти обычным массивом. Не трудно понять, что это часто может быть действительно недостатком. Хотя на практике векторы бывают быстрее и эффективнее обычных массивов.
Вывести на экран вектор можно очень разнообразными способами, один из выглядит вот так:


В листинге #g происходит следующее: cout представляет собой объект, т. е. обычную переменную определённого типа. Следовательно в эту переменную можно запихать данные. Чтобы запихать данные, нужно, чтобы тип данных совпадал с типом, могущимя храниться в объекте. cout является объектом класса ostream, вот и видим, что ostream_iterator, т. е. итератор для класса ostream, под объект, хранящий int значения (значения типа int хранит вектор, следовательно копировать надо значения с типом int). Так и происходит копирование значений в cout, после чего мы видим результат на экране.

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

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

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/valeriy-faronov-6004364/turbo-pascal-7-0-uchebnyy-kurs-11649619/?lfrom=15589587
Яндекс.Метрика