Vector in C++: Dynamic Arrays, 2D Vectors

This article explains what vectors are, why they matter, and how to use them confidently—with crystal‑clear code, outputs, and “Try it yourself” challenges after each section for hands‑on learning.

🔹 What is a Vector in C++?

A Vector in C++ is a dynamic array from the Standard Template Library (STL) that automatically grows and shrinks as elements are added or removed. Unlike fixed‑size arrays, vectors manage memory for you and provide a clean, consistent interface for safe, readable code.

#include <vector>
#include <iostream>
using namespace std;
int main() {
    vector<int> nums;         // empty vector
    nums.push_back(10);            // add one element
    nums.push_back(20);            // add another
    cout << "Size: " << nums.size() << "\n";
    cout << "First: " << nums << "\n";
}

Output

Size: 2
First: 10

Try it yourself

  • Add another element and print back() to show the last value.
  • Replace nums with nums.at(0) and try nums.at(5) to see the bounds‑check error message in action.

🔹 Why use Vector in C++?

  • Flexible: Grows and shrinks as needed—no manual resizing.
  • Safe access: at() checks bounds; [] is fast but unchecked.
  • STL‑friendly: Works with algorithms, iterators, and range‑for loops.
  • Performance: Amortized constant‑time appends via capacity doubling; contiguous memory for cache‑friendly access.

🔹 Creating and Initializing a Vector in C++

Here are common ways to create and initialize a Vector in C++, from empty to pre‑filled containers.

#include <vector>
#include <iostream>
using namespace std;
int main() {
    vector<int> a;                // empty
    vector<int> b(5);             // size 5, default-initialized (0 for int)
    vector<int> c(3, 42);         // size 3, all elements 42
    vector<int> d{1, 2, 3, 4};    // initializer list
    vector<string> names{"Ava", "Noah"}; // vector of strings
    cout << "c size: " << c.size() << "\n";
    cout << "d: " << d << "\n";
    cout << "names: " << names << "\n";
}

Output

c size: 3
d: 3[10]
names: Ava

Try it yourself

  • Create vector<double> prices(4, 9.99) and print all elements.
  • Use vector<int> e = d to copy and then modify e; verify d is unchanged (deep copy).

🔹 Adding, Accessing, and Removing Elements

Use push_back and emplace_back to append, front/back to peek ends, and pop_back to remove from the end. Prefer at() for safe indexed reads in beginner code.

#include <vector>
#include <iostream>
#include <string>
using namespace std;
int main() {
    vector<string> todo;
    todo.push_back("learn vector");      // copy string
    todo.emplace_back("practice push");  // construct in place
    cout << "First: " << todo.front() << "\n";
    cout << "Last : " << todo.back() << "\n";
    todo.pop_back();                     // remove last
    cout << "Size after pop: " << todo.size() << "\n";
    // Safe access
    if (!todo.empty()) {
        cout << "Item 0: " << todo.at(0) << "\n";
    }
}

Output

First: learn vector
Last : practice push
Size after pop: 1
Item 0: learn vector

Try it yourself

  • Replace push_back("learn vector") with string s = "learn vector"; todo.push_back(s) and explain copy vs in‑place construction.
  • Call todo.clear() and check empty() and size().

🔹 Iterating a Vector in C++

Iterate using range‑for for simplicity, classic indices for random access, or iterators for STL algorithms.

#include <vector>
#include <iostream>
using namespace std;
int main() {
    vector<int> v{2, 4, 6, 8};
    // Range-for (read-only)
    for (int x : v) {
        cout << x << " ";
    }
    cout << "\n";
    // Index-based (read/write)
    for (size_t i = 0; i < v.size(); ++i) {
        v[i] += 1;
    }
    // Iterator-based
    for (auto it = v.begin(); it != v.end(); ++it) {
        cout << *it << " ";
    }
    cout << "\n";
}

Output

2 4 6 8
3 5 7 9

Try it yourself

  • Use const auto& in range‑for to avoid copies: for (const auto& x : v).
  • Print elements in reverse using rbegin()/rend().

🔹 Size, Capacity, reserve, and shrink_to_fit

A Vector in C++ keeps a logical size (number of elements) and a physical capacity (allocated slots). Appending beyond capacity triggers a reallocation. Use reserve(n) to pre‑allocate and reduce reallocations; shrink_to_fit() requests trimming extra capacity.

#include <vector>
#include <iostream>
using namespace std;
int main() {
    vector<int> v;
    cout << "size=" << v.size() << ", cap=" << v.capacity() << "\n";
    v.reserve(100); // pre-allocate
    cout << "after reserve cap=" << v.capacity() << "\n";
    for (int i = 0; i < 50; ++i) {
        v.push_back(i);
    }
    cout << "after push size=" << v.size() << ", cap=" << v.capacity() << "\n";
    v.shrink_to_fit(); // request trim
    cout << "after shrink cap=" << v.capacity() << "\n";
}

Output

size=0, cap=0
after reserve cap=100
after push size=50, cap=100
after shrink cap=50

Try it yourself

  • Remove reserve(100) and print capacity growth as you push elements (notice how it expands).
  • Call v.resize(10) to shrink size without changing capacity; print both to compare.

🔹 Inserting and Erasing Anywhere

Use insert/emplace to add elements at a position and erase to remove by iterator or range. Note that inserting/erasing in the middle can move elements and may invalidate iterators/pointers/references.

#include <vector>
#include <iostream>
using namespace std;
int main() {
    vector<int> v{1, 2, 3, 4, 5};
    v.insert(v.begin() + 1, 99);        // insert 99 at index 1
    v.emplace(v.end(), 77);              // emplace 77 at the end
    v.erase(v.begin() + 2);              // remove the '2'
    v.erase(v.begin() + 2, v.begin() + 4); // remove a range: 3 and 4
    for (int x : v) {
        cout << x << " ";
    }
    cout << "\n";
}

Output

1 99 5 77

Try it yourself

  • Insert three 0s at position 1 using v.insert(v.begin() + 1, 3, 0).
  • Erase all even numbers using a loop with iterators (be careful to update the iterator after erase).

🔹 2D Vector in C++ (Vector of Vectors)

A Vector in C++ can itself store other vectors, which is a clean way to make dynamic 2D (or ND) grids without manual memory management.

#include <vector>
#include <iostream>
using namespace std;
int main() {
    int rows = 3;
    int cols = 4;
    vector<vector<int>> grid(rows, vector<int>(cols, 0));
    for (const auto& row : grid) {
        for (int x : row) {
            cout << x << " ";
        }
        cout << "\n";
    }
}

Output

0 0 0 0
0 0 0 0
0 0 0 0

Try it yourself

  • Resize to 5×5 using grid.assign(5, vector<int>(5, -1)) and set a diagonal.
  • Build an adjacency list: vector<vector<int>> adj(n) and push neighbors for each node.

🔹 Interop with Algorithms (sort, find, accumulate)

Vectors play perfectly with standard algorithms for searching, sorting, and aggregating, producing clear and efficient code.

#include <vector>
#include <iostream>
#include <algorithm>
#include <numeric>
using namespace std;
int main() {
    vector<int> v{5, 1, 4, 2, 3};
    sort(v.begin(), v.end());
    auto it = find(v.begin(), v.end(), 4);
    bool found = (it != v.end());
    int sum = accumulate(v.begin(), v.end(), 0);
    cout << "sorted: ";
    for (int x : v) {
        cout << x << " ";
    }
    cout << "\n";
    cout << "found 4? " << boolalpha << found << "\n";
    cout << "sum: " << sum << "\n";
}

Output

sorted: 1 2 3 4 5
found 4? true
sum: 15

Try it yourself

  • Remove duplicates using sort + unique + erase.
  • Partition odds and evens with stable_partition and print both groups.

🔹 Best Practices for Vector in C++

  • Prefer vector over raw arrays for safety, clarity, and STL compatibility.
  • Use reserve when the final size is known to reduce reallocations.
  • Pass large vectors by const& to avoid copies; return by value (RVO/move) is fine.
  • Use emplace_back for objects to avoid extra copies when constructing in place.
  • Check empty() before front()/back(), and prefer at() in teaching/beginner code.

🔹 Common Pitfalls and How to Avoid Them

  • Invalidated references: Reallocation can move elements, invalidating pointers/iterators/references—avoid storing them across push_back/insert without reserve.
  • Out‑of‑range access: [] is unchecked—use at() when unsure.
  • Capacity confusion: resize changes size (constructs/destroys elements); reserve changes only capacity.
  • Frequent mid‑vector edits: Insert/erase at the front/middle shifts elements—consider deque/linked structures if this dominates.
  • vector<bool>: It’s a space‑optimized specialization with proxy elements; prefer vector<char>/vector<uint8_t> if you need real references.

🔹 Mini Project: Scoreboard with Vector

This mini project uses a Vector in C++ to track scores: add, list, sort, and compute stats.

#include <vector>
#include <iostream>
#include <algorithm>
#include <numeric>
#include <iomanip>
using namespace std;
int main() {
    vector<int> scores;
    scores.push_back(88);
    scores.push_back(92);
    scores.push_back(75);
    scores.push_back(96);
    scores.push_back(83);
    cout << "Scores: ";
    for (int s : scores) {
        cout << s << " ";
    }
    cout << "\n";
    sort(scores.begin(), scores.end());
    double avg = accumulate(scores.begin(), scores.end(), 0.0) / scores.size();
    cout << "Sorted: ";
    for (int s : scores) {
        cout << s << " ";
    }
    cout << "\n";
    cout << fixed << setprecision(2);
    cout << "Average: " << avg << "\n";
    cout << "Best: " << scores.back() << "\n";
    cout << "Worst: " << scores.front() << "\n";
}

Output

Scores: 88 92 75 96 83
Sorted: 75 83 88 92 96
Average: 86.80
Best: 96
Worst: 75

Try it yourself

  • Add erase to remove any score < 80, then recompute average.
  • Read scores from input until EOF and handle empty input safely.

🔹 FAQ: Vector in C++

Q1. What’s the difference between size() and capacity()?
size() is the number of elements currently stored; capacity() is how many could be stored before reallocating memory.

Q2. When should I use reserve()?
Use reserve() when the final number of elements is known (or roughly known) to avoid repeated reallocations and iterator invalidation.

Q3. Is vector contiguous in memory?
Yes. Elements are stored contiguously, which is great for cache performance and interop with C APIs via data().

Q4. Should I use [] or at()?
[] is unchecked and fast; at() throws on out‑of‑range. For teaching and debugging, prefer at(); switch to [] when bounds are guaranteed.

Q5. Why did my iterators become invalid?
Any operation that reallocates (e.g., growth beyond capacity, inserts) can move elements, invalidating pointers, references, and iterators. Call reserve() or reacquire iterators after modification.

Q6. What’s the complexity of push_back?
Amortized constant time. Occasional growth operations cost more, but averaged over many inserts, the cost per push is O(1).

Q7. Is vector<bool> safe to use?
It’s specialized to pack bits and returns proxy objects; most beginners should prefer vector<char> or vector<uint8_t> for boolean flags to avoid surprises.

Q8. How do I make a 2D dynamic array?
Use vector<vector<T>> for simplicity, or a single vector<T> with manual indexing if you need strict contiguity across rows.

🔹 Wrapping Up

Mastering Vector in C++ unlocks clean, safe, and high‑performance data handling. Prefer vectors over raw arrays, use reserve() to control reallocations, iterate with range‑for for clarity, and lean on STL algorithms for powerful one‑liners. Don’t forget the “Try it yourself” challenges above to turn knowledge into muscle memory.

Leave a Comment

About RadiantRiva

Your go-to resource for coding tutorials, developer guides, and programming tips.

Learn More

Quick Links

Follow Us

Newsletter

Get coding tips, tutorials, and updates straight to your inbox.