vector
std::vector<T> is a dynamic, resizable array, and it is the default, go-to sequence container in C++. Unless you have a specific reason to reach for something else, vector should be your first choice for storing a collection of elements — it has the best combination of performance, cache-friendliness, and ease of use of any standard container.
Creating and initializing
vector_create.cpp
CPP
#include <vector>
#include <iostream>
int main() {
std::vector<int> empty; // empty vector
std::vector<int> fromList = {1, 2, 3, 4}; // initializer list
std::vector<int> repeated(5, 0); // five zeros: {0,0,0,0,0}
std::vector<int> copy(fromList); // copy of another vector
std::cout << fromList.size() << std::endl; // 4
return 0;
}Adding and removing elements
vector_push_pop.cpp
CPP
#include <vector>
#include <iostream>
int main() {
std::vector<int> nums;
nums.push_back(10);
nums.push_back(20);
nums.push_back(30); // nums = {10, 20, 30}
nums.pop_back(); // removes 30 -> nums = {10, 20}
std::cout << nums.size() << std::endl; // 2
std::cout << nums.empty() << std::endl; // 0 (false)
nums.clear(); // removes all elements
std::cout << nums.empty() << std::endl; // 1 (true)
return 0;
}Accessing elements: [] vs. at()
Vectors support the familiar [] indexing operator, but it performs no bounds checking — reading or writing past the end is undefined behavior, not a caught error. .at() does the same job but checks the index and throws std::out_of_range if it's invalid.
vector_access.cpp
CPP
#include <vector>
#include <iostream>
#include <stdexcept>
int main() {
std::vector<int> nums = {10, 20, 30};
std::cout << nums[1] << std::endl; // 20 — fast, unchecked
try {
std::cout << nums.at(10) << std::endl; // throws std::out_of_range
} catch (const std::out_of_range& e) {
std::cout << "Caught: " << e.what() << std::endl;
}
return 0;
}operator[] does not check bounds
nums[10] on a 3-element vector will not throw — it silently reads or writes memory outside the vector's buffer, which is undefined behavior and a classic source of hard-to-diagnose bugs and security vulnerabilities. Use .at() when the index isn't already guaranteed valid (for example, when it comes from user input), and reserve [] for hot loops where you've already established the index is in range.
Iterating
vector_iterate.cpp
CPP
#include <vector>
#include <iostream>
int main() {
std::vector<std::string> names = {"Alice", "Bob", "Carol"};
for (const auto& name : names) {
std::cout << name << std::endl;
}
return 0;
}Amortized O(1) push_back
A vector stores its elements in one contiguous block of heap memory. When that block is full and you call push_back again, the vector must allocate a larger block (typically double the previous capacity), copy or move every existing element into it, and free the old block. That single operation is O(n) — but it happens rarely, and the cost is spread out (“amortized”) across all the cheap O(1) push_backs that happen between reallocations. Averaged over many calls, push_back behaves as if it were O(1).
reserve() — avoiding reallocations
If you know roughly how many elements you'll end up storing, calling .reserve(n) up front allocates enough capacity for n elements immediately, so subsequent push_back calls don't need to reallocate at all. This is a simple, very common performance optimization.
vector_reserve.cpp
CPP
#include <vector>
#include <iostream>
int main() {
std::vector<int> nums;
nums.reserve(1000); // allocate capacity for 1000 elements up front
for (int i = 0; i < 1000; i++) {
nums.push_back(i); // no reallocations happen during this loop
}
std::cout << nums.size() << std::endl; // 1000 (actual element count)
std::cout << nums.capacity() << std::endl; // >= 1000 (allocated space)
return 0;
}size() vs. capacity()
.size() is the number of elements actually stored; .capacity() is how many elements fit in the currently allocated block before another reallocation is needed. Capacity is always greater than or equal to size.
vector is the default choice
Prefer std::vector unless you have a specific, demonstrated need for another container's characteristics (frequent insertion in the middle, insertion at the front, or key-based lookup). Its contiguous memory layout makes it by far the most cache-friendly standard container, which usually makes it faster in practice than more “theoretically appropriate” containers even for operations it's not specifically optimized for.