std::vector — The Go-To Container
std::vector is a dynamically-sized array: it stores elements in contiguous memory (like a C array) but grows automatically as you add elements. It is the most-used container in the C++ standard library, and in most situations where you need a list of things, std::vector is the right choice. It offers fast random access by index, efficient appending at the end, and automatic memory management — no new or delete required.
Creating vectors
std::vector is a class template: you specify the element type in angle brackets. There are several ways to construct one.
#include <vector>
std::vector<int> empty; // empty vector, size 0
std::vector<int> scores = {85, 92, 78, 95}; // initializer list
std::vector<int> tenZeros(10, 0); // 10 elements, all 0
std::vector<double> fives(5, 3.14); // 5 copies of 3.14
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
std::cout << scores.size(); // 4 — number of elements
std::cout << scores.empty(); // falseAccessing elements
Elements are accessed by zero-based index, exactly like arrays. The [] operator does not bounds-check. The at() method throws std::out_of_range on invalid indices. front() and back() give the first and last elements.
std::vector<int> v = {10, 20, 30, 40, 50};
int first = v[0]; // 10
int last = v[v.size()-1]; // 50
int front = v.front(); // 10
int back = v.back(); // 50
// Modify an element:
v[2] = 99; // {10, 20, 99, 40, 50}
// Bounds-checked access:
try {
int oob = v.at(10); // throws std::out_of_range
} catch (const std::out_of_range& e) {
std::cout << "Out of range!\n";
}Adding and removing elements
push_back() appends an element to the end. pop_back() removes the last element. emplace_back() constructs the element in-place (more efficient than constructing a temporary and pushing it). Inserting or erasing in the middle is possible but O(n) — all subsequent elements must be shifted.
std::vector<int> v;
v.push_back(10); // {10}
v.push_back(20); // {10, 20}
v.push_back(30); // {10, 20, 30}
v.pop_back(); // {10, 20} — removes last element
// emplace_back constructs directly in the vector:
std::vector<std::string> words;
words.emplace_back("hello"); // slightly more efficient than push_back
// Insert at a position (expensive — O(n)):
v.insert(v.begin() + 1, 15); // {10, 15, 20}
// Erase at a position (also O(n)):
v.erase(v.begin() + 1); // {10, 20}
// Clear all elements:
v.clear(); // size becomes 0, capacity unchangedIterating over a vector
All three loop styles work with vectors. The range-based for loop is the cleanest for most use cases.
std::vector<int> scores = {85, 92, 78, 95, 61};
// Range-based for — cleanest:
for (int s : scores) {
std::cout << s << " ";
}
// const ref — no copy, can't modify:
for (const auto& s : scores) {
std::cout << s << " ";
}
// Index-based — use when you need the index:
for (std::size_t i = 0; i < scores.size(); ++i) {
std::cout << i << ":" << scores[i] << " ";
}
// Modify each element:
for (auto& s : scores) {
s += 5; // adds 5 to every score in place
}Size vs capacity — how growth works
A vector has two counts: size (the number of elements currently stored) and capacity (the number of elements the internal buffer can hold before the vector needs to reallocate). When you push_back and size reaches capacity, the vector allocates a new larger buffer (typically doubling), copies all elements over, and releases the old one. This is O(n) but happens rarely — amortized, each push_back is O(1).
std::vector<int> v;
std::cout << v.size() << "/" << v.capacity(); // 0/0
v.push_back(1); // allocates buffer
v.push_back(2);
v.push_back(3);
std::cout << v.size() << "/" << v.capacity(); // 3/4 (typical)
// Reserve capacity up front to avoid reallocations:
std::vector<int> big;
big.reserve(1000); // allocates space for 1000 elements now
for (int i = 0; i < 1000; ++i) {
big.push_back(i); // no reallocation — capacity was pre-allocated
}
// Resize — changes the size (adds default-constructed elements if growing):
v.resize(6, 0); // {1, 2, 3, 0, 0, 0}Use reserve(n) when you know you will be adding approximately n elements — it avoids repeated reallocations and the associated copies.
Quick reference
| Operation | Code |
|---|---|
| Size | v.size() |
| Empty check | v.empty() |
| Access element | v[i] or v.at(i) |
| First / last | v.front() / v.back() |
| Append | v.push_back(x) or v.emplace_back(...) |
| Remove last | v.pop_back() |
| Insert at pos | v.insert(v.begin() + i, x) |
| Erase at pos | v.erase(v.begin() + i) |
| Clear all | v.clear() |
| Pre-allocate | v.reserve(n) |
| Resize | v.resize(n, fill_value) |
| Pointer to data | v.data() (raw array pointer) |
| Sort | std::sort(v.begin(), v.end()) |
| Find element | std::find(v.begin(), v.end(), val) |
Key rules to remember
std::vector is the default container — reach for it unless you have a reason not to
Contiguous memory gives vector the best cache performance of any container. It outperforms linked lists for almost all practical workloads.
[] does not bounds-check — out-of-range access is undefined behavior
If the index might be invalid, use at() which throws instead of corrupting memory.
Use reserve() when you know the approximate final size
Pre-reserving capacity avoids repeated reallocations (each of which copies all elements) during a push_back loop.
Do not invalidate iterators or references while iterating
push_back may cause a reallocation that invalidates all existing iterators and pointers into the vector. Never push_back inside a range-for over the same vector.
Pass vector by const reference to avoid copying
void f(const std::vector<int>& v) avoids copying the entire buffer. The copy is O(n) — expensive for large vectors.