std::vector в C++: понятное руководство для начинающих с примерами и советами

Если вы начинаете путь в C++, умение уверенно работать со std::vector — один из важнейших навыков. Вектор сочетает динамический массив, безопасность и удобные алгоритмы. Ниже — практическое руководство для запроса «std::vector в C++: руководство с примерами» с акцентом на повседневные задачи и ошибки, которые совершают новички.
Что такое std::vector и когда его использовать
std::vector — динамический массив: элементы лежат подряд в памяти, а размер может расти по мере добавления данных. Подходит для списков, буферов, коллекций сущностей, когда нужен быстрый произвольный доступ по индексу и эффективная итерация.
#include <vector>
#include <iostream>
int main() {
std::vector<int> v; // пустой вектор
v.push_back(10); // добавить элемент в конец
v.emplace_back(20); // сконструировать элемент на месте
std::vector<int> a(5, 42); // 5 элементов со значением 42
std::vector<int> b = {1, 2, 3}; // список инициализации
std::cout << v[0] << " " << v[1] << "\n"; // 10 20
}
Доступ к элементам: operator[] vs at()
operator[] не проверяет границы (быстро, но потенциально опасно). at() бросает исключение std::out_of_range — безопаснее при отладке.
std::vector<int> v = {1, 2, 3};
int x = v[2]; // OK, без проверки
int y = v.at(2); // OK, с проверкой
// int z = v.at(5); // std::out_of_range
Итерация без лишних копий
В range-based for выбирайте ссылку, чтобы не копировать элементы. Для чтения — const&.
std::vector<std::string> names = {"Ann", "Bob", "Cara"};
for (const auto& s : names) {
// читаем без копий
}
for (auto& s : names) {
s += "!"; // изменяем на месте
}
size, capacity, reserve и shrink_to_fit
size — число элементов. capacity — выделенная память (может быть больше size). Добавления могут вызывать перераспределение памяти (reallocation), которое инвалидирует указатели, итераторы и ссылки на элементы.
std::vector<int> v;
std::cout << v.size() << " " << v.capacity() << "\n";
v.reserve(1000); // заранее выделяем память под ~1000 эл-тов
for (int i = 0; i < 1000; ++i) v.push_back(i);
std::cout << v.size() << " " << v.capacity() << "\n";
v.shrink_to_fit(); // пытаемся ужать capacity до size (не обяз.)
Совет: если примерно знаете конечное количество элементов — вызовите reserve. Это уменьшит число реаллокаций и ускорит программу.
push_back vs emplace_back
push_back добавляет уже готовый объект. emplace_back конструирует объект прямо в памяти вектора — полезно для сложных типов.
struct User { std::string name; int age; };
std::vector<User> users;
users.push_back(User{"Ann", 20}); // конструирование + перемещение/копия
users.emplace_back("Bob", 30); // конструирование на месте (обычно быстрее)
Удаление элементов: erase и идиома erase-remove
erase удаляет по итератору или диапазону, сдвигая хвост. Чтобы удалить все элементы по условию, используйте std::remove_if + erase.
#include <algorithm>
std::vector<int> v = {1,2,3,4,5,6};
// Удалить все чётные
v.erase(std::remove_if(v.begin(), v.end(), [](int x){ return x % 2 == 0; }), v.end());
// Удалить один элемент по индексу i
size_t i = 2;
if (i < v.size()) v.erase(v.begin() + static_cast<ptrdiff_t>(i));
Инвалидированные ссылки и итераторы
При росте capacity вектор переносит элементы в новую память. Все указатели/итераторы/ссылки на элементы становятся недействительными. Пример ошибки:
std::vector<int> v;
v.reserve(1);
v.push_back(1);
int* p = &v[0]; // указатель на элемент
v.push_back(2); // возможно reallocation! p теперь недействителен
// *p - неопределённое поведение
Решение: хранить индексы, а не указатели; заранее резервировать память; пере-вычислять итераторы после операций, меняющих capacity.
Передача std::vector в функции
- Только читаем — передавать как const std::vector<T>&.
- Изменяем — std::vector<T>&.
- Забираем владение/перемещаем — std::vector<T>&& (с std::move при вызове).
int sum(const std::vector<int>& v) {
int s = 0; for (int x : v) s += x; return s;
}
void fillZeros(std::vector<int>& v) {
std::fill(v.begin(), v.end(), 0);
}
void take(std::vector<int>&& v) {
// владеем временным вектором, можем переместить данные дальше
}
Правильная инициализация 2D-вектора
Чтобы создать матрицу r×c, используйте конструктор с размером и значением по умолчанию. Не путайте reserve и resize.
size_t r = 3, c = 4;
std::vector<std::vector<int>> m(r, std::vector<int>(c, 0));
m[1][2] = 5;
// Ошибка новичков: reserve не создаёт элементы!
std::vector<int> v;
v.reserve(10);
// v[0] = 1; // UB: нет элементов. Сначала resize:
v.resize(10);
v[0] = 1; // OK
Сортировка, уникализация и удаление дубликатов
Комбинация sort + unique + erase позволяет быстро убрать повторы.
#include <algorithm>
std::vector<int> v = {3,1,2,3,2,1,4};
std::sort(v.begin(), v.end());
v.erase(std::unique(v.begin(), v.end()), v.end());
// v: 1,2,3,4
Производительность: 8 коротких советов
- Если знаете примерный размер — делайте v.reserve(N).
- Используйте emplace_back для сложных объектов (меньше лишних копий).
- Итерируйтесь по const auto& для избежания копирования крупных элементов.
- Не используйте erase в середине вектора в горячих циклах — возможно, лучше другой контейнер.
- После крупного clear можно v.shrink_to_fit(), если важна экономия памяти.
- Не храните долгоживущие указатели/итераторы на элементы, если вектор растёт.
- Для добавления многих элементов рассмотрите резервирование + вставку батчами (insert).
- Передавайте вектор по ссылке, если не нужно копирование.
Мини-практика: читаем числа, фильтруем и считаем сумму
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> v; v.reserve(1000);
int x; while (std::cin >> x) v.push_back(x);
// Оставим только положительные
v.erase(std::remove_if(v.begin(), v.end(), [](int t){ return t <= 0; }), v.end());
std::sort(v.begin(), v.end());
v.erase(std::unique(v.begin(), v.end()), v.end());
long long sum = 0; for (int t : v) sum += t;
std::cout << "count=" << v.size() << " sum=" << sum << "\n";
}
Частые ошибки новичков
- Путаница между reserve и resize: reserve выделяет память, но не создаёт элементов; resize — создаёт/удаляет элементы, меняет size.
- Использование operator[] вне диапазона — неопределённое поведение; для проверки используйте at().
- Хранение указателей/итераторов на элементы после увеличения объёма — они инвалидируются.
- Удаление в цикле через erase без аккуратной работы с итераторами — легко пропустить элементы или получить UB. Пользуйтесь erase-remove или возвращаемым итератором erase.
Куда двигаться дальше
Освоив std::vector, вы быстрее поймёте остальные контейнеры STL и алгоритмы. Хотите системно прокачаться на практике? Посмотрите курс с заданиями и разбором ошибок — Пройти «C++ с Нуля до Гуру» и вывести навыки на новый уровень.
Итог: std::vector в C++ — базовый, но невероятно мощный инструмент. Зная различия между size и capacity, правила invalidate и паттерн erase-remove, вы пишете безопаснее и быстрее.
-
-
Михаил Русаков
Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.