Standard Template Library

Vectors and Arrays

Master C++ sequence containers — C arrays, std::array, std::vector, deque, capacity management, 2D vectors, and choosing the right container.

Vectors and Arrays

Sequence containers store elements in a linear order. C++ provides several: C-style arrays (low-level), std::array (fixed-size, stack-allocated), and std::vector (dynamic, heap-allocated). std::vector is the most used container in C++ — it is the default choice unless you have a specific reason for something else.


C-Style Arrays

cpp
#include <iostream>
using namespace std;

int main() {
    // Fixed size — must be known at compile time (or use VLAs, which are non-standard)
    int arr[5] = {10, 20, 30, 40, 50};

    // Access
    cout << arr[0] << "\n";   // 10
    arr[2] = 99;

    // No bounds checking — arr[10] is undefined behaviour!
    int len = sizeof(arr) / sizeof(arr[0]);   // 5 — element count trick
    cout << "Length: " << len << "\n";

    // C arrays decay to pointer when passed to functions
    // — size information is lost!

    return 0;
}

Use C arrays only when interfacing with C code or embedded systems. In all other cases, prefer std::array or std::vector.


std::array — Fixed-Size, Stack-Allocated

cpp
#include <iostream>
#include <array>
#include <algorithm>
using namespace std;

int main() {
    array<int, 5> a = {3, 1, 4, 1, 5};

    // Size known at compile time — no runtime overhead
    cout << a.size()  << "\n";   // 5
    cout << a.front() << "\n";   // 3
    cout << a.back()  << "\n";   // 5
    cout << a.at(2)   << "\n";   // 4 (bounds-checked)

    // Ranged for
    for (int n : a) cout << n << " ";
    cout << "\n";  // 3 1 4 1 5

    // Sort
    sort(a.begin(), a.end());
    for (int n : a) cout << n << " ";
    cout << "\n";  // 1 1 3 4 5

    // Fill
    a.fill(0);

    return 0;
}

std::vector — Dynamic Array

cpp
#include <iostream>
#include <vector>
using namespace std;

int main() {
    // Construction
    vector<int> v1;                    // Empty
    vector<int> v2(5, 0);             // 5 zeros: {0,0,0,0,0}
    vector<int> v3 = {1, 2, 3, 4, 5}; // Initializer list
    vector<int> v4(v3);               // Copy of v3

    // Size and capacity
    cout << v3.size()     << "\n";   // 5 — current element count
    cout << v3.capacity() << "\n";   // >= 5 — allocated space
    cout << v3.empty()    << "\n";   // 0 (false)

    // Element access
    cout << v3[0]    << "\n";   // 1  (no bounds check)
    cout << v3.at(4) << "\n";   // 5  (throws if out of range)
    cout << v3.front() << "\n"; // 1
    cout << v3.back()  << "\n"; // 5

    return 0;
}

Adding and Removing Elements

cpp
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v;

    // Add to back
    v.push_back(10);
    v.push_back(20);
    v.push_back(30);
    // v = {10, 20, 30}

    // emplace_back — constructs in-place (avoids extra copy)
    v.emplace_back(40);

    // Remove from back
    v.pop_back();
    // v = {10, 20, 30}

    // Insert at position
    auto it = v.begin() + 1;
    v.insert(it, 15);
    // v = {10, 15, 20, 30}

    // Erase at position
    v.erase(v.begin() + 2);
    // v = {10, 15, 30}

    // Erase range
    v.erase(v.begin(), v.begin() + 2);
    // v = {30}

    // Clear all elements
    v.clear();
    cout << v.size() << "\n";   // 0

    return 0;
}

Capacity Management

cpp
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v;

    // reserve — pre-allocate space (avoids reallocations)
    v.reserve(1000);
    cout << v.capacity() << "\n";  // 1000
    cout << v.size()     << "\n";  // 0

    for (int i = 0; i < 1000; i++) v.push_back(i);
    cout << v.size()     << "\n";  // 1000
    cout << v.capacity() << "\n";  // 1000 — no reallocation happened!

    // shrink_to_fit — release unused capacity
    v.shrink_to_fit();

    // Doubling growth strategy (typical implementation)
    vector<int> w;
    for (int i = 0; i < 10; i++) {
        w.push_back(i);
        cout << "size=" << w.size() << " cap=" << w.capacity() << "\n";
    }
    // Capacity: 1, 2, 4, 4, 8, 8, 8, 8, 16, 16...

    return 0;
}

Note: When you know the number of elements in advance, always call reserve() before the insertion loop. This prevents O(n) reallocation copies.


2D Vectors

cpp
#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 3×4 matrix initialised to 0
    int rows = 3, cols = 4;
    vector<vector<int>> matrix(rows, vector<int>(cols, 0));

    // Fill with values
    for (int r = 0; r < rows; r++)
        for (int c = 0; c < cols; c++)
            matrix[r][c] = r * cols + c + 1;

    // Print
    for (const auto& row : matrix) {
        for (int val : row) cout << val << "\t";
        cout << "\n";
    }
    // 1  2  3  4
    // 5  6  7  8
    // 9  10 11 12

    // Add a row
    matrix.push_back({13, 14, 15, 16});
    cout << "Rows: " << matrix.size() << "\n";   // 4

    return 0;
}

Searching and Modifying

cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v = {5, 2, 8, 1, 9, 3, 7, 4, 6};

    // Find
    auto it = find(v.begin(), v.end(), 9);
    if (it != v.end()) {
        cout << "Found 9 at index: " << distance(v.begin(), it) << "\n";  // 4
    }

    // Sort
    sort(v.begin(), v.end());
    // v = {1, 2, 3, 4, 5, 6, 7, 8, 9}

    // Binary search (requires sorted vector)
    bool found = binary_search(v.begin(), v.end(), 7);
    cout << "Contains 7: " << found << "\n";  // 1

    // Remove elements matching predicate
    v.erase(remove_if(v.begin(), v.end(),
                      [](int x) { return x % 2 == 0; }),  // Remove evens
            v.end());
    for (int n : v) cout << n << " ";
    cout << "\n";  // 1 3 5 7 9

    return 0;
}

Vector of Structs vs Struct of Vectors

cpp
#include <vector>
#include <string>
using namespace std;

// Array of Structures (AoS) — cache-friendly for accessing all fields of one record
struct Particle_AoS {
    float x, y, z;
    float vx, vy, vz;
    float mass;
};
vector<Particle_AoS> particles_aos;

// Structure of Arrays (SoA) — cache-friendly for accessing one field across all records
struct Particles_SoA {
    vector<float> x, y, z;
    vector<float> vx, vy, vz;
    vector<float> mass;
};
// SoA is faster when processing one field at a time (e.g., physics update loops)

Practical Example: Histogram

cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <iomanip>
using namespace std;

void printHistogram(const vector<int>& data, int bins = 10) {
    int minVal = *min_element(data.begin(), data.end());
    int maxVal = *max_element(data.begin(), data.end());
    int range  = maxVal - minVal + 1;
    int binW   = max(1, (range + bins - 1) / bins);

    vector<int> counts(bins, 0);
    for (int val : data) {
        int bin = min((val - minVal) / binW, bins - 1);
        counts[bin]++;
    }

    int maxCount = *max_element(counts.begin(), counts.end());

    for (int i = 0; i < bins; i++) {
        int lo = minVal + i * binW;
        int hi = lo + binW - 1;
        int bars = counts[i] * 50 / maxCount;

        cout << setw(4) << lo << "-" << setw(4) << hi << " | "
             << string(bars, '#')
             << " " << counts[i] << "\n";
    }
}

int main() {
    // Generate sample data (exam scores)
    vector<int> scores;
    for (int score : {72,85,91,68,78,95,82,61,77,88,
                       90,74,83,56,92,79,87,65,71,98}) {
        scores.push_back(score);
    }

    cout << "Score Distribution:\n";
    printHistogram(scores, 5);
    // 56- 63 | #########  2
    // 64- 71 | ###############  3
    // 72- 79 | ###############################  6
    // 80- 87 | #######################  5
    // 88- 95 | #######################  4 (approx)

    return 0;
}

Summary

  • C arrays — fixed size, no bounds checking, decay to pointers — avoid in modern C++
  • std::array<T,N> — fixed size, stack-allocated, full STL interface — prefer over C arrays
  • std::vector<T> — dynamic size, heap-allocated, amortised O(1) push_back — the default container
  • Call reserve(n) before bulk insertions to avoid repeated reallocations
  • Use emplace_back instead of push_back to construct objects in-place
  • erase(remove_if(...), end()) is the idiomatic way to remove matching elements from a vector
  • 2D vectors are vector<vector<T>> — each inner vector can have a different size (jagged)

Next, we'll explore maps and sets — associative containers for key-value pairs and unique element collections.