Vector - Dynamic Arrays
Master std::vector, the most versatile and commonly used STL container for dynamic array management
Learn how to use std::vector, the most versatile and commonly used container in C++, for dynamic array management with automatic memory handling.
A Simple Example
#include <iostream>
#include <vector>
int main() {
// Creating vectors
std::vector<int> numbers; // Empty vector
std::vector<int> primes{2, 3, 5, 7}; // Initialize with values
std::vector<int> zeros(5); // 5 elements, all zero
std::vector<int> fives(5, 100); // 5 elements, all 100
// Adding elements
numbers.push_back(10);
numbers.push_back(20);
numbers.push_back(30);
// Accessing elements
std::cout << "First: " << numbers[0] << "\n"; // No bounds check
std::cout << "Second: " << numbers.at(1) << "\n"; // With bounds check
// Size and capacity
std::cout << "Size: " << numbers.size() << "\n"; // 3
std::cout << "Capacity: " << numbers.capacity() << "\n"; // >= 3
// Iterating
for (const auto& num : numbers) {
std::cout << num << " ";
}
std::cout << "\n";
return 0;
}
Breaking It Down
Creating and Initializing Vectors
-
Empty vector:
std::vector<int> numbers;creates a vector with no elements -
With values:
std::vector<int> primes{2, 3, 5, 7};initializes with specific values -
Fixed size:
std::vector<int> zeros(5);creates 5 zero-initialized elements -
With value:
std::vector<int> fives(5, 100);creates 5 elements, all set to 100
Adding and Accessing Elements
-
push_back(): Adds elements to the end of the vector -
Operator
[]: Fast access without bounds checking (unsafe) -
Method
at(): Access with bounds checking, throws exception if out of range -
Remember: Use
at()when safety is critical,[]when performance matters
Size vs Capacity
-
size(): Returns the number of elements currently stored -
capacity(): Returns the total allocated space before reallocation needed - Capacity is always >= size to minimize reallocations
-
Use
reserve(n)to pre-allocate capacity when you know the final size
Iterating Through Vectors
-
Range-based for loop:
for (const auto& item : vec)is clean and safe -
Index-based:
for (size_t i{0}; i < vec.size(); ++i)when you need indices -
Iterators:
for (auto it = vec.begin(); it != vec.end(); ++it)for advanced operations -
Remember: Use
const auto&to avoid unnecessary copies
Why This Matters
- std::vector is the most important container in C++. It combines the performance of arrays with automatic memory management, handles resizing automatically, and provides bounds checking in debug mode.
- It's the recommended default container choice unless you have a specific reason to use something else. Understanding vector is essential for writing modern C++ code.
Critical Insight
Vector doesn't reallocate every time you add an element. It grows by a factor (usually 1.5x or 2x) when it runs out of capacity. This means push_back() is O(1) amortized - most insertions are fast, and occasional reallocations are offset by the growth strategy.
If you know the final size, use reserve() to pre-allocate and avoid all reallocations. This is the secret to high-performance vector usage.
Best Practices
Use reserve() when you know the size: Call vec.reserve(1000) before adding elements to avoid multiple reallocations. This significantly improves performance for large vectors.
Prefer at() during development: Use at() for bounds checking while debugging. Switch to [] for performance-critical production code after verification.
Use emplace_back() for complex objects: Instead of push_back(), use emplace_back() to construct objects in-place, avoiding unnecessary copies.
Pass vectors by const reference: Use void func(const std::vector<int>& vec) to avoid expensive copies when passing vectors to functions.
Common Mistakes
Using [] Without Checking Bounds: vector[100] on a 10-element vector is undefined behavior. Use at() for safety or check size() first.
Iterator Invalidation: After push_back(), insert(), or erase(), iterators may be invalidated. Always refresh iterators after modifying the vector.
Comparing size() to signed int: Use vec.size() returns size_t (unsigned). Comparing with negative numbers or signed int can cause unexpected results.
Shrinking to fit: After removing elements, capacity doesn't decrease automatically. Use vec.shrink_to_fit() to release unused memory if needed.
Debug Challenge
This code tries to access an element that doesn't exist. Click the highlighted line to fix it:
Quick Quiz
- What's the difference between
size()andcapacity()?
- What's the time complexity of
push_back()?
- Which method should you use for safe element access with bounds checking?
Practice Playground
Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.
Output:
Error:
Lesson Progress
- Fix This Code
- Quick Quiz
- Practice Playground - run once