Эффективное использование vector в C++
материал взят с сайта artlang Данная статья рассчитана на неискушенного программиста C++. С другой стороны любой, кто программирует на C++ обязан знать то, что описывается ниже. Речь пойдет об одном из самых популярных контейнеров в STL. А именно, о векторе ( std::vector ). С него, обычно, начинается описание контейнеров в книгах по языку C++ и STL.
Считается, что это наиболее простой контейнер, с понятным интерфейсом и предсказуемым временем операций. Однако, за описанием интерфейса часто “теряются” основные правила при работе с вектором, которые могут быть не так очевидны для начинающего программиста. Написать свой хороший вектор крайне сложно — такие реализации далеки от совершенства, значит надо знать как работают стандартные. Для закрепления материала предлагаю пройти тест на понимание std::vector.
Внутреннее устройство вектора
Джосаттис в своей книге описывает вектор так: “Этот контейнер моделирует динамический массив. Таким образом, вектор — это абстракция, управляющая своими элементами в стиле динамического массива из языка C. Однако в стандарте не указано, что реализация вектора использует динамический массив. Скорее это следует из ограничений и спецификаций сложности его операций”. Вектор представляет собой шаблонный класс в пространстве имен std:
Программирование на С++. Урок 70. Вектор
namespace std < template < typename T, typename Allocator = allocator> class vector; >
Второй необязательный шаблонный параметр задает модель памяти. По умолчанию используется шаблонный класс allocator . Этот класс предоставляет стандартная библиотека C++, и именно он отвечает за выделение и освобождение памяти. Если вектору в качестве второго параметра передать какой-либо другой распределитель памяти ( allocator ), то вектор, возможно, уже будет основан не на C-массиве. Однако, обычно второй параметр остается заданным по умолчанию, поэтому рассмотрим подробнее стандартный распределитель памяти — std::allocator . Он, кстати, используется по умолчанию для всех контейнеров библиотеки STL. Вектору (и всем другим контейнерам STL) от аллокатора нужно прежде всего, чтобы он выделял и освобождал некоторую область памяти, в тот момент когда это требуется контейнеру, Стандартный аллокатор делает это так:
template class allocator < public: typedef T value_type; typedef value_type *pointer; typedef const value_type *const_pointer typedef size_t size_type; pointer allocate(size_type _Count) // Выделяем память для _Count элементов < // типа value_type void *_Ptr = 0; if (_Count == 0) // Если ничего не запросили, то ничего и не делаем ; else if (((size_t)(-1) / sizeof (value_type) < _Count) || (_Ptr = ::operator new(_Count * sizeof (value_type))) == 0) < throw bad_alloc(); // Выделение памяти не удалось >return ((pointer)_Ptr); > void deallocate(pointer _Ptr, size_type) < ::operator delete(_Ptr); // Освобождение памяти >// Остальная реализация не приводится, чтобы сохранить наглядность примера >;
Так аллокатор реализован у Microsoft (MSVC), и также он реализован в GCC. Нужно понимать, что operator new отличается от просто new . Вызов new (который все мы используем) на самом деле разбивается на два этапа, если так можно выразиться: сначала вызывается operator new, который возвращает указатель на некоторую выделенную область памяти, а затем вызывается конструктор, который создает объект в этой области. Вызвать конструктор напрямую невозможно, однако с помощью синтаксиса размещения можно заставить компилятор вызвать конструктор. Следующие два примера создания foo1 и foo2 идентичны:
#include class Foo < public: Foo() <>>; int main(int argc, char** argv) < // Вот так мы все привыкли создавать объект в «куче»: Foo *foo1 = new Foo(); // Выделение памяти + Вызов конструктора // А вот какие вызовы происходят на самом деле: void *ptr = operator new(sizeof(Foo)); // Выделение памяти Foo *foo2 = ::new (ptr) Foo(); // Вызов конструктора, синтаксис размещения return 0; >
- первый (first) указатель указывает на начало выделенной области памяти
- второй (last) указатель указывает на позицию следующую за последним элементом, хранящимся в выделенной области памяти
- третий (end) указывает на “конец” выделенной области памяти
Они очень просто инициализируются сразу после того, как аллокатор выделит память:
// _Capacity — Количество элементов, которое может быть размещено в памяти вектора first = Getal().allocate(_Capacity); // Getal() возвращает ссылку на аллокатор last = first; // В начале вектор пуст, затем last начинает смещаться к end end = first + _Capacity; // Граница выделенного участка памяти, пересекать её нельзя!
Посмотрите теперь, как лаконично и кратко реализуются некоторые функции-члены вектора:
size_t capacity() const < // return current length of allocated storage return (end — first); >size_t size() const < // return length of sequence return (last — first); >bool empty() const < // test if sequence is empty return (first == last); >const_reference at(size_t pos) const < // subscript nonmutable sequence with checking if (last — first const_reference operator[](size_t pos) const < // subscript nonmutable sequence return (*(first + pos)); >// Сплошная арифметика указателей.
Когда программист добавляет в конец вектора новый элемент происходит примерно следующее:
void push_back(const value_type // insert element at end if (last == end) < // Если память закончилась, // то нужно запросить новую область, больше предыдущей // размер текущей области памяти size_t _Capacity = capacity(); // требуемый минимальный размер новой области size_t _Count = size() + 1; if (max_size() — _Capacity / 2 < _Capacity) < // новая область больше на 50% предыдущей _Capacity = _Capacity + _Capacity / 2; >if (_Capacity < size() + 1) < // Если нет возможности увеличить на 50%, то // пробуем выделить минимально необходимый размер _Capacity = _Count; >_Reallocate(_Capacity); // Перераспределение памяти, значения fisrt, last и end // будут изменены! Предыдущий участок памяти будет освобожден. // В новый участок будут скопированы все данные из предыдущего. // Это не быстрый процесс. > // Вызов копирующего конструктора, синтаксис размещения ::new ((void *)last) value_type(val); ++last; // Сдвигаем указатель с учетом вставленного элемента >
Данный код взят из исходников STL MSVC, но он не сильно отличается от аналогичного кода в GCC. Код был несколько упрощен, чтобы суть алгоритма не была “размыта” и оставалась ясной.
Итак. Вектор гарантирует (по стандарту), что вставка в конец происходит очень быстро за одно и тоже время. Однако если мы попадаем под условие last == end (если выделенная память закончилась) то время вставки в конец сильно возрастает. То на сколько затянется процесс трудно спрогнозировать и это в большей степени зависит от конструкторов копирования и деструкторов элементов, находящихся в векторе. Так как они будут вызваны для каждого существующего элемента в векторе при перераспределении памяти.
Приемы эффективного использования
В предыдущем примере хорошо видно, как реализация вектора “пытается бороться” с неэффективным использование памяти. Проблема возникает, когда память, выделенная аллокатором, заканчивается. В этот момент вектор запрашивает у аллокатора новый участок памяти, больше предыдущего, которой смог бы разместить все элементы уже содержащиеся в векторе, а также новые.
Для этого повторно выделяется память из “кучи”. Операция эта достаточно медленная, и к тому же блокирующая в общем случае (heap lock — это “узкое место” многопоточных приложений). Таких операций должно быть как можно меньше.
Поэтому вектор запрашивает память “про запас”, чтобы не инициировать новое перераспределение памяти при добавлении следующего элемента. Если бы вектор на каждый push_back вызывал перераспределение памяти, он был бы очень медленным. Вместо этого, когда память оказывается исчерпана, вектор запрашивает на 50% больше памяти, чем у него было. Так он уменьшает количество повторных обращений к аллокатору и, тем самым, перераспределений памяти.
При перераспределении памяти, в общем случае, вызываются копирующие конструкторы элементов вектора, так как из “старой” памяти их нужно корректно перенести в новую выделенную область, А вслед за конструкторами вызываются деструкторы элементов, так как из “старой” памяти их нужно правильно удалить. Если в векторе хрянятся POD данные, то конечно будет задействована оптимизация при их копировании в новую область, однако это скорее частный случай, нежели общая практика.
Рассмотрим пример, в котором мы заполним вектор одним миллионом объектов класса Foo . Будем считать, что к Foo не применяется оптимизация при копировании:
class Foo < public: explicit Foo(long long) <>Foo(const Foo> ~Foo() <> >; int main(int argc, char** argv) < std::vectorvec; for (long long ii = 0; ii < 1000000; ++ii) < vec.push_back(Foo(ii)); >return 0; >
По выходу из блока for , оказалось что:
- копирующий конструктор был вызван более 3 миллионов раз
- деструктор был вызван более 3 миллионов раз
- перераспределение памяти произошло 35 раз (в начале емкость вектора была равна 0)
Ни одна из этих миллионов операций нам не была нужна. Поэтому попробуем избавиться от них.
reserve(num)
reserve — это функция-член вектора, которая увеличивает его емкость, то есть принудительно запрашивает у аллокатора область памяти такого размера, чтобы вектор, при желании, мог разместить в ней num элементов. Вызывать её стоит сразу после создания объекта вектора, пока в нем еще нет элементов. Допустим Вы можете не знать точного числа элементов, но Вы как минимум можете предположить это значение, затем накиньте еще 50% (если нет проблем с памятью) и это значение передайте reserve. Вызвать reserve имеет смысл практически всегда, даже если число элементов не большое. Так Вы поможете вектору избежать множества лишних шагов. Если в примере, сразу после создания объекта вектора, вызвать reserve:
std::vector vec; vec.reserve(1000000); // Сразу зарезервируем место под миллион элементов
то по выходу из блока for , окажется что:
- копирующий конструктор был вызван ровно 1 миллион раз
- деструктор был вызван ровно 1 миллион раз
- перераспределение памяти не происходило ни разу
Отличный результат! Миллионы лишних вызовов удалось избежать, к тому же перераспределение памяти ни разу не произошло! А всё потому что вектору хватило памяти разместить все наши элементы. Всегда помните об этом, и вызывайте reserve, после создания вектора.
Это полезно еще и потому, что стандатрный механизм выделения “про запас” (на 50% больше, чем было) иногда может привести к избыточному выделению памяти. Много лишнего, это тоже плохо. Лучше контролируйте этот процесс сами.
emplace_back(args)
Идем дальше. Порой удается избежать вообще вызовов копирующего конструктора при добавлении нового элемента. Начиная со стандарта C++11 появилась функция-член emplace_back , которая конструирует элемент прямо на месте, где его и предполагалось разместить. При этом не вызывается ни копирующий конструктор, ни перемещающий. Аргументы, переданные функции emplace_back , точно также передаются затем конструктору элемента. Перепишем добавление элементов в примере, задействова emplace_back :
std::vector vec; vec.reserve(1000000); // Сразу зарезервируем место под миллион элементов for (long long ii = 0; ii < 1000000; ++ii) < // Передаем аргументы так, будто вызываем обычный конструктор Foo vec.emplace_back(ii); >
По выходу из блока for , оказалось что:
- копирующий конструктор не вызывался ни разу
- деструктор не вызывался ни разу
- перераспределение памяти не происходило ни разу
Чтобы разместить один миллион элементов Foo , потребовалось только вызвать миллион раз конструктор данного типа, что вполне логично. Напомним, что до предпринятых действий, было более шести миллионов ненужных вызовов и несколько перераспределений памяти.
POD
Еще одним способом оптимизировать работу с вектором, является использование POD (“plain old data”) типов в качестве элементов. Так как вектор управляет непрерывным участком памяти, он может применять тривиальные функции копирования, наподобие memcpy. Новый стандарт C++11 несколько расширил понятие POD. Обязательно ознакомьтесь с этими простыми структурами данных, и смело используйте их в качестве элеметов вектора, так как для них будет применяться оптимизация при копировании.
Добавляйте в конец и меньше ищите
Если Вам часто нужно что-то искать в векторе, то возможно стоит подумать о выборе другого контейнера. Поиск по несортированной последовательности крайне неэффективен, но еще более неэффективно пытаться держать вектор отсортированным. Например, вызвав std::sort Вы инициируете неизвестное Вам количество вызовов копирующего конструктора и такое же количество деструкторов. В худшем случае это число может стремиться к общему числу элементов в векторе.
Что же касается вставки элемента в вектор, то здесь можно легко подсчитать чего это будет стоить. Для всех элементов, находящихся за позицией вставки, будут вызваны по разу копирующий конструктор и деструктор. На нашем примере, если мы решим вставить новый элемент вот таким образом:
// . после цикла for vec.insert(vec.begin() + 500000, Foo(1));
- 500000 ненужных нам вызовов копирующего конструктора
- 500000 ненужных нам вызовов деструктора
Вот такая плата. Поэтому, если Вы часто вставляете элементы не в конец вектора, возможно стоит подумать о выборе другого контейнера.
Итог
Вектор — это отличный контейнер, который подходит для размещения очень большого количества элементов. Он умеет резервировать память, что отличает его от остальных контейнеров, и это нужно использовать. POD данные — идеальные кандидаты на элементы вектора, так как к ним вектор применяет простые и быстрые методы копирования. Начиная со стандарта C++11, мы можем конструировать объекты непосредственно на том месте, где они будут храниться, если будем использовать emplace_back. Это экономит вызовы копирующих конструкторов.
Вектор плохо подходит для поиска и вставкам элементов в произвольное место (эффективно добавляются элементы только в конец). Помните, при грамотном использовании вектора, Вы всегда получите быстродействие сравнивнимое с кодом на чистом C, при этом у Вас будет удобный интерфейс и автоматическое управление памятью, в придачу. Такого Вам не сможет дать ни один язык — одновременно и быстродействие, и удобство программирования. Если же быстродействие Вам не нужно — то, возможно, Вам не нужен и C++.
Источник: pro-prof.com
Векторы в C++ — урок 12
Вектор в C++ — это замена стандартному динамическому массиву, память для которого выделяется вручную, с помощью оператора new .
Разработчики языка рекомендуют в использовать именно vector вместо ручного выделения памяти для массива. Это позволяет избежать утечек памяти и облегчает работу программисту.
- Пример создания вектора
- Управление элементами вектора
- Методы класса vector
Пример создания вектора
#include int main() < // Вектор из 10 элементов типа int std::vectorv1(10); // Вектор из элементов типа float // С неопределенным размером std::vector v2; // Вектор, состоящий из 10 элементов типа int // По умолчанию все элементы заполняются нулями std::vector v3(10, 0); return 0; >
Управление элементами вектора
Создадим вектор, в котором будет содержаться произвольное количество фамилий студентов.
#include #include int main() < // Поддержка кириллицы в консоли Windows setlocale(LC_ALL, «»); // Создание вектора из строк std::vectorstudents; // Буфер для ввода фамилии студента std::string buffer = «»; std::cout 0) < // Добавление элемента в конец вектора students.push_back(buffer); >> while (buffer != «»); // Сохраняем количество элементов вектора unsigned int vector_size = students.size(); // Вывод заполненного вектора на экран std::cout return 0; >
Результат работы программы:
Методы класса vector
Для добавления нового элемента в конец вектора используется метод push_back() . Количество элементов определяется методом size() . Для доступа к элементам вектора можно использовать квадратные скобки [] , также, как и для обычных массивов.
- pop_back() — удалить последний элемент
- clear() — удалить все элементы вектора
- empty() — проверить вектор на пустоту
Подробное описание всех методов std::vector (на английском) есть на C++ Reference.
Источник: code-live.ru
Введение в вектора в C++
Если до сих пор вы пользовались «чистыми» массивами в языке С++, вы многое потеряли. Под «чистыми» массивами я подразумеваю обычное использование массивов в С++, без специальных функций и методов. Прочитав эту статью, вы узнаете как можно работать с массивами на более высоком уровне, вы сможете обрабатывать массивы (объявление, инициализация, поиск, сортировка и многие другие операции) буквально несколькими строчками.
Итак, что же такое «Вектор» в языке С++? Простыми словами вектор можно описать как абстрактную модель, которая имитирует динамический массив. Пока не стоит углубляться в это определение, сейчас мы приступим к практике и вам все станет понятно.
Если мы хотим использовать векторы в своей программе, необходимо подключить заголовочный файл :
#include
Вектор можно объявить следующим образом:
std::vector myVector; // мы создали пустой вектор типа int myVector.reserve(10); // тут мы зарезервировали память под 10 элементов типа int
Как видно из примера, вектора относятся к пространству имен std . По сути, эти две записи эквивалентны такой записи:
int myVector[10]; // обычное объявление массива
На первый взгляд, объявление вектора оказалось намного более громоздкое. Однако вектора скрывают очень мощный функционал, чего нельзя сказать об обычных массивах С++. Кроме того, вектор можно объявить и в одной строке, вот так:
std::vector myVector(10);
Эта запись эквивалентна двум предыдущим, то есть здесь мы объявили вектор с начальным размером в 10 элементов типа int . Но кроме этого, такой способ объявления вектора не просто выделяет память, но и еще инициализирует все элементы вектора нулями. Вот пример:
#include #include // подключаем модель Векторов using namespace std; int main() < vectormyVector(10); // объявляем вектор размером в 10 элементов и инициализируем их нулями // вывод элементов вектора на экран for(int i = 0; i
Обратите внимание на то, что размер вектора определяется методом size() , это очень удобно, если мы не знаем размер массива. Вывод:
CppStudio.com
0 0 0 0 0 0 0 0 0 0
Если объявить вектор таким образом:
vector myVector; // объявляем пустой вектор myVector.reserve(10); // выделяем память под 10 элементов
то результат работы программы будет другим, в потоке вывода ничего не появится, так как нет начальной инициализации элементов вектора, а значит этот способ объявления вектора выполнится быстрее. Именно в этом и заключается разница этих способов объявления векторов.
Несколькими абзацами выше, я упомянул о начальном размере вектора. Почему же начальный размер? Потому, что, если размера вектора будет не хватать, вектор автоматически будет увеличиваться, при добавлении новых элементов, пересчитывая свой размер. Это очень удобно, так как за частую мы не можем предугадать размер массива, который нам нужен для работы программы. Более подробно мы рассмотрим этот пример немного позже.
Смотрите как легко можно скопировать вектор:
#include #include // подключаем модель Векторов using namespace std; int main() < vectormyVector1(10); // вывод элементов вектора на экран cout cout << «nСкопированный массив: «; vectormyVector2(myVector1); // при объявлении второго вектора, копируется — первый for(int i = 0; i < myVector2.size(); i++) < myVector2[i] = i; cout return 0; >
CppStudio.com
Входной массив: 0 1 2 3 4 5 6 7 8 9 Скопированный массив: 0 1 2 3 4 5 6 7 8 9
Из результат работы программы хорошо видно ,что в строке 14, была создана копия вектора myVector1 . Рассмотрим программу, в которой сравниваются два массива:
#include #include using namespace std; int main() < vectorarray1(3); // инициализируем элементы вектора array1 array1[0] = 4; array1[1] = 2; array1[2] = 1; vector array2(3); // инициализируем элементы вектора array2 array2[0] = 4; array2[1] = 2; array2[2] = 1; // сравниваем массивы if (array1 == array2) < cout return 0; >
CppStudio.com
array1 == array2
Итак, массивы мы инициализировали обыкновенным для нас способом, строки 8-10 и 13-15. Самое удивительное то, что операция сравнивания векторов выполняется в одну строку, строка 17. Попробуйте сделать то же самое с обычными массивами в С++, уверен, что у вас ничего не получится.
До этого, во всех примерах в этой статье я выводил элементы массива используя цикл, с векторами можно обойтись и без него. Смотрим как именно это делается.
#include #include #include // заголовочный файл итераторов using namespace std; int main() < vectorarray1; // создаем пустой вектор // добавляем в конец вектора array1 элементы 4, 3, 1 array1.insert(array1.end(), 4); array1.insert(array1.end(), 3); array1.insert(array1.end(), 1); // вывод на экран элементов вектора copy( array1.begin(), // итератор начала массива array1.end(), // итератор конца массива ostream_iterator(cout,» «) //итератор потока вывода ); return 0; >
CppStudio.com
4 3 1
Итак, начнем по порядку. В строке 3 я добавил новый заголовочный файл, для использования итераторов. Так как в строке 8 мы создали пустой вектор, то конец вектора — это его начало, ведь в векторе нет никаких элементов.
Так что, когда мы добавляем новые элементы в массив, мы должны использовать итератор array1.end() , а не итератор array1.begin() . Иначе порядок элементов в массиве станет противоположным. В строках 10-12 мы используем метод insert(), который позволяет вставить элемент в массив. Ну и самое главное, вывод элементов массива выполняется не через цикл а через операцию copy() . В первых двух параметрах мы указали итераторы начала и конца вектора. В третьем параметре указан поток вывода cout — ostream_iterator(cout,» «) . Как по мне, такой способ организации вывода на экран намного красивее выглядит, хотя, возможно сразу и не понятен для новичка. Но вы просто постарайтесь его принять как должное и запомнить.
Источник: cppstudio.com
10.23 – Знакомство с std::vector
В предыдущем уроке мы представили std::array , который обеспечивает функциональность встроенных фиксированных массивов C++ в более безопасной и удобной форме.
Аналогичным образом стандартная библиотека C++ предоставляет функциональные возможности, которые делают более безопасной и простой работу с динамическими массивами. Это средство называется std::vector .
В отличие от std::array , который точно соответствует базовой функциональности фиксированных массивов, std::vector содержит некоторые дополнительные возможности. Это помогает сделать std::vector одним из самых полезных и универсальных инструментов в вашем наборе инструментов C++.
Знакомство с std::vector
Представленный в C++03, std::vector предоставляет функциональность динамического массива, которая обеспечивает собственное управление памятью. Это означает, что вы можете создавать массивы, длина которых устанавливается во время выполнения, без необходимости явно выделять и освобождать память с помощью операторов new и delete . std::vector находится в заголовке .
Объявить std::vector просто:
#include // в объявлении не нужно указывать длину std::vector array; // использовать список инициализаторов для инициализации массива (до C++11) std::vector array2 = < 9, 7, 5, 3, 1 >; // использовать унифицированную инициализацию для инициализации массива std::vector array3 < 9, 7, 5, 3, 1 >; // как и в случае с std::array, тип может быть опущен, начиная с C++17 std::vector array4 < 9, 7, 5, 3, 1 >; // вывести к std::vector
Обратите внимание, что как в неинициализированном, так и в инициализированном случае вам не нужно указывать длину массива во время компиляции. Это связано с тем, что std::vector будет динамически выделять память для своего содержимого, сколько потребуется.
Как и в std::array , доступ к элементам массива может осуществляться с помощью оператора [] (который не проверяет границы) или функции at() (которая выполняет проверку границ):
array[6] = 2; // без проверки границ array.at(7) = 3; // проверяет границы
В любом случае, если вы запрашиваете элемент, который находится за пределами массива, вектор не будет автоматически изменять свой размер.
Начиная с C++11, вы также можете присваивать значения std::vector , используя список инициализаторов:
array = < 0, 1, 2, 3, 4 >; // ok, длина массива теперь 5 array = < 9, 8, 7 >; // ok, длина массива теперь 3
В этом случае вектор автоматически изменит размер, чтобы соответствовать количеству предоставленных элементов.
Самоочистка предотвращает утечку памяти
Когда переменная вектор выходит за пределы области видимости, она (при необходимости) автоматически освобождает память, которую она контролирует. Это не только удобно (поскольку вам не нужно делать это самостоятельно), но и помогает предотвратить утечки памяти. Рассмотрим следующий фрагмент:
void doSomething(bool earlyExit) < int *array< new int[5] < 9, 7, 5, 3, 1 >>; // выделяем память с помощью new if (earlyExit) return; // выходит из функции без освобождения памяти, выделенной выше // здесь что-то делаем delete[] array; // никогда не вызывается >
Если для earlyExit установлено значение true , массив никогда не будет освобожден, и произойдет утечка памяти.
Однако если array является std::vector , этого не произойдет, потому что память будет освобождена, как только массив выйдет за пределы области видимости (независимо от того, завершится функция раньше или нет). Это делает std::vector более безопасным в использовании, чем самостоятельное выделение памяти.
Векторы запоминают свою длину
В отличие от встроенных динамических массивов, которые не знают длину массива, на который они указывают, std::vector отслеживает свою длину. Мы можем запросить длину вектора с помощью функции size() :
#include #include void printLength(const std::vector std::cout int main() < std::vector array < 9, 7, 5, 3, 1 >; printLength(array); return 0; >
Приведенный выше пример печатает:
The length is: 5
Как и в случае с std::array , size() возвращает значение вложенного типа size_type (полный тип в приведенном выше примере будет std::vector::size_type ), который является целочисленным значением без знака.
Изменение размера вектора
Изменение размера встроенного динамически размещаемого массива сложно. Изменить размер std::vector так же просто, как вызвать функцию resize() :
#include #include int main() < std::vector array < 0, 1, 2 >; array.resize(5); // устанавливаем размер 5 std::cout
Этот код печатает:
The length is: 5 0 1 2 0 0
Здесь следует отметить два момента. Во-первых, когда мы изменили размер вектора, значения существующих элементов были сохранены! Во-вторых, новые элементы инициализируются значением по умолчанию для типа (которое для int равно 0).
Размер векторов может быть уменьшен:
#include #include int main() < std::vector array < 0, 1, 2, 3, 4 >; array.resize(3); // устанавливаем размер 3 std::cout
Этот код печатает:
The length is: 3 0 1 2
Изменение размера вектора требует больших вычислительных ресурсов, поэтому вы должны стремиться минимизировать количество таких операций. Если вам нужен вектор с определенным количеством элементов, но вы не знаете значений элементов в момент объявления, вы можете создать вектор со значениями элементов по умолчанию:
#include #include int main() < // Используя прямую инициализацию, мы можем создать вектор из 5 элементов, // каждый элемент равен 0. Если мы используем инициализацию с фигурными // скобками, у вектора будет 1 элемент со значением 5. std::vectorarray(5); std::cout
Эта программа печатает:
The length is: 5 0 0 0 0 0
Мы поговорим о том, почему прямая инициализация и инициализация с фигурными скобками обрабатываются по-разному в уроке «16.7 – Список инициализаторов std::initializer_list ». Практическое правило: если тип представляет собой какой-то список, и вы не хотите инициализировать его списком, используйте прямую инициализацию.
Уплотнение логических значений
У std::vector есть еще один крутой трюк. Существует специальная реализация для std::vector типа bool , которая сжимает 8 логических значений в один байт! Это происходит за кулисами и не меняет способ использования std::vector .
#include #include int main() < std::vectorarray < true, false, false, true, true >; std::cout
Этот код печатает:
The length is: 5 1 0 0 1 1
Еще не всё
Обратите внимание, что это вводная статья, предназначенная для ознакомления с основами std::vector . В уроке «11.11 – Емкость и стековое поведение std::vector » мы рассмотрим некоторые дополнительные возможности std::vector , включая разницу между длиной и емкостью вектора, и более подробно рассмотрим, как std::vector обрабатывает выделение памяти.
Заключение
Поскольку переменные типа std::vector обеспечивают собственное управление памятью (что помогает предотвратить утечки памяти), запоминают свою длину и могут легко изменять размер, мы рекомендуем использовать std::vector в большинстве случаев, когда необходимы динамические массивы.
Источник: radioprog.ru
Vector C++
Размер указывать заранее не нужно. Память будет выделяться по мере необходимости, иначе говоря, динамически.
К каждому элементу вектора легко получить доступ зная его порядковый номер.
Теорию можно изучить, например, в Википедии
Пример
1. vector vec0 size is 0 vector vec0 is empty
2. vector vec size is 5 vector vec is not empty
После запуска программы результат будет таким:
1. vector vec0 size is 0 vector vec0 is empty 2. vector vec size is 5 vector vec is not empty 3. 4 4 4 4 4 4. 4 4 4 4 4 5. new elements are added with push_back 6. 4 4 4 4 4 7 8 9 8. 4 4 4 4 4 7 9. vec_two size is 6 10. 4 4 4 4 4 7 11. identical C:Usersaosourcereposvectors1.exe (process 111800) exited with code 0. To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops. Press any key to close this window . . .
Задача
Создать вектор целых чисел vi от 0 до 9 и вывести на экран.
Вывести на экран размер вектора vi.
#include #include #include using namespace std; int main() < vectorvi; for ( int i = 0; i < 10; i++) < vi.push_back(i); >for ( auto item : vi) < cout cout
0 1 2 3 4 5 6 7 8 9 int vector vi has 10 elements. C:Usersaosourcereposvector_00.exe (process 101408) exited with code 0. To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops. Press any key to close this window . . .
Вручную меняем значения нескольких элементов
vi[5] = 3; vi[6] = -1; vi[1] = 99;
Выведем изменённый вектор на экран другим способом:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 C:Usersaosourcereposvector_00.exe (process 101408) exited with code 0. To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops. Press any key to close this window . . .
Вычислим сколько элементов вектора равны 3. Для вывода вектора на экран воспользуемся новым способом.
#include #include #include #include // for sort and count using namespace std; int main() < vectorvi; for ( int i = 0; i < 10; i++) < vi.push_back(i); >vi[5] = 3; vi[6] = -1; vi[1] = 99; for ( auto i = begin(vi); i != end(vi); i++) < cout cout
0 99 2 3 4 3 -1 7 8 9 vector of ints has 2 elements with value 3 C:Usersaosourcereposvector_00.exe (process 4108) exited with code 0. To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops. Press any key to close this window . . .
Задача
Записать три слова, введённые с клавиатуры в вектор, вывести их на экран, отсортировать по алфавиту, определить сколько в первом слове букв о.
enter three words heihei andreyolegovich topbicycle heihei andreyolegovich topbicycle andreyolegovich heihei topbicycle first word has 2 letter o’s C:Userswd973579sourcereposPlural_02DebugPlural_02.exe (process 93128) exited with code 0. To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops. Press any key to close this window . . .
Как Вы можете увидеть слова отсортированы по алфавиту. В названии сайта andreyolegovich две буквы о.
Вывести по отдельности элементы
Задача: дана строка с числами через запятую.
Нужно:
вывести числа на экран по отдельности.
вычислить сумму чисел
После запуска программы результат будет таким:
12 1 214 3234 12 1 214 3234 Сумма чисел в строке = 3461 C:Usersaosourcereposvector_01.exe (process 144128) exited with code 0. To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops. Press any key to close this window . . .
Создать двухмерный Vector C++
Задача: создать двухмерный вектор 3 на 5 и заполнить его случайными числами от 0 до 9
После запуска программы результат будет таким:
1 7 4 0 9 4 8 8 2 4 5 5 1 7 1 C:Usersaosourcereposvector_02.exe (process 145912) exited with code 0. To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops. Press any key to close this window . . .
Видео
В видео ниже есть всё, что описано в статье кроме более быстрого перебора элеметов вектора.
Источник: www.andreyolegovich.ru