На попередньому уроці ми розглядали std::array, який є безпечнішою і зручнішою формою звичайних фіксованих масивів в мові C++. Аналогічно, в Стандартній бібліотеці C++ є поліпшена версія динамічних масивів (безпечніша і зручніша) — std::vector.
На відміну від std::array, який недалеко відійшов від базового функціоналу звичайних фіксованих масивів, std::vector поставляється з додатковими можливостями, які роблять його одним з найбільш корисних і універсальних інструментів в мові C++.
Вектори
Представлений в C++03, std::vector (або просто “вектор”) — це той же динамічний масив, але який може сам керувати виділеною йому пам’яттю. Це означає, що ви можете створювати масиви, довжина яких задається під час виконання, без використання операторів new і delete (явної вказівки на виділення і звільнення пам’яті). std::vector знаходиться в заголовку vector. Оголошення std::vector наступне:
1 2 3 4 5 6 |
#include <vector> // Немає необхідності вказувати довжину при ініціалізації std::vector<int> array; std::vector<int> array2 = { 10, 8, 6, 4, 2, 1 }; // використовується список ініціалізаторів для ініціалізації масиву std::vector<int> array3 { 10, 8, 6, 4, 2, 1 }; // використовується uniform-ініціалізація для ініціалізації масиву (починаючи з C++11) |
Зверніть увагу, що як і в неініціалізованому, так і в ініціалізованому випадках вам не потрібно явно вказувати довжину масивів. Це пов’язано з тим, що std::vector динамічно виділяє пам’ять для свого вмісту за запитом.
Подібно std::array, доступ до елементів масиву може виконуватися як через оператор []
(який не виконує перевірку діапазону), так і через функцію at() (яка виконує перевірку діапазону):
1 2 |
array[7] = 3; // без перевірки діапазону array.at(8) = 4; // з перевіркою діапазону |
У будь-якому випадку, якщо ви будете надавати запит на елемент, який знаходиться поза діапазоном array
, довжина вектора автоматично змінюватися не буде. Починаючи з C++11, ви також можете присвоювати значення для std::vector, використовуючи список ініціалізаторів:
1 2 |
array = { 0, 2, 4, 5, 7 }; // ок, довжина array тепер 5 array = { 11, 9, 5 }; // ок, довжина array тепер 3 |
В такому випадку вектор самостійно змінюватиме свою довжину, щоб відповідати кількості наданих елементів.
Скажи “Ні!” витокам пам’яті!
Коли змінна-вектор виходить з області видимості, вона автоматично звільняє пам’ять, яку контролювала (займала). Це не тільки зручно (так як вам не потрібно це робити вручну), але також допомагає запобігти витокам пам’яті. Розглянемо наступний фрагмент:
1 2 3 4 5 6 7 8 9 10 11 |
void doSomething(bool value) { int *array = new int[7] { 12, 10, 8, 6, 4, 2, 1 }; if (value) return; // Робимо що-небудь delete[] array; // якщо value == true, то цей стейтмент ніколи не виконається } |
Якщо змінній value
присвоїти значення true
, то array
ніколи не буде видалений, пам’ять ніколи не буде звільнена і станеться витік пам’яті.
Однак, якби array
був вектором, то подібне ніколи б і не сталося, оскільки пам’ять звільнялася б автоматично при виході array
з області видимості (незалежно від того, чи вийде функція раніше з області видимості чи ні). Саме через це використання std::vector є безпечнішим, ніж динамічне виділення пам’яті через оператор new.
Довжина векторів
На відміну від стандартних динамічних масивів, які не знають свою довжину, std::vector свою довжину запам’ятовує. Щоб її дізнатися, потрібно використати функцію size():
1 2 3 4 5 6 7 8 9 10 |
#include <vector> #include <iostream> int main() { std::vector<int> array { 12, 10, 8, 6, 4, 2, 1 }; std::cout << "The length is: " << array.size() << '\n'; return 0; } |
Результат:
The length is: 7
Змінити довжину стандартного динамічно виділеного масиву досить проблематично і складно. Змінити довжину std::vector так само просто, як викликати функцію resize():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <vector> #include <iostream> int main() { std::vector<int> array { 0, 1, 2 }; array.resize(7); // змінюємо довжину array на 7 std::cout << "The length is: " << array.size() << '\n'; for (auto const &element: array) std::cout << element << ' '; return 0; } |
Результат:
The length is: 7
0 1 2 0 0 0 0
Тут є дві речі, на які слід звернути увагу. По-перше, коли ми змінили довжину array
, існуючі значення елементів збереглися! По-друге, нові елементи були ініціалізовані значенням за замовчуванням у відповідність з певним типом даних (значенням 0
для типу int).
Довжину вектора також можна змінити і в зворотний бік (обрізати):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <vector> #include <iostream> int main() { std::vector<int> array { 0, 1, 4, 7, 9, 11 }; array.resize(4); // змінюємо довжину array на 4 std::cout << "The length is: " << array.size() << '\n'; for (auto const &element: array) std::cout << element << ' '; return 0; } |
Результат:
The length is: 4
0 1 4 7
Зміна довжини вектора є витратною операцією, тому рекомендується мінімізувати кількість подібних виконуваних операцій.
Висновки
Це вступна стаття, призначена для ознайомлення з основами std::vector. На наступних уроках ми детально розглянемо std::vector, в тому числі і різницю між довжиною і ємністю вектора, і те, як в std::vector виконується виділення пам’яті.
Оскільки змінні типу std::vector можуть самі керувати виділеною їм пам’яттю (що допомагає запобігти витокам пам’яті), відстежують свою довжину і легко її змінюють, то рекомендується використовувати std::vector замість стандартних динамічних масивів.