Step 1: A fixed-size array holds a known number of readings

#include <iostream>

int main() {
    int readings[3]{10, 20, 30};
    for (int i = 0; i < 3; ++i) {
        std::cout << readings[i] << ' ';
    }
}

Three readings, three slots, one loop bound. Everything is fixed before the program runs. int readings[3] = {10, 20, 30}; (copy-list-initialization, with =) gives the same array; the two forms differ only for class types with explicit constructors, which we meet when we study classes and constructors.

Step 2: Add a dimension — rows and columns need two indices

#include <iostream>

int main() {
    int image[2][3]{{10, 20, 30}, {40, 50, 60}};
    for (int row = 0; row < 2; ++row) {
        for (int col = 0; col < 3; ++col) {
            std::cout << image[row][col] << ' ';
        }
        std::cout << '\n';
    }
}

Added: a second bound and an inner loop. Each index is checked against its own dimension.

Step 3: Delete the fixed bound — std::vector grows as input arrives

#include <iostream>
#include <vector>

int main() {
    std::vector<int> readings;
    int value;
    while (std::cin >> value) {
        readings.push_back(value);
    }
    for (std::size_t i = 0; i < readings.size(); ++i) {
        std::cout << readings[i] << ' ';
    }
}

Deleted: the literal 3. The count is now readings.size(), known only at run time.

Step 4: Swap [] for at() — make an invalid index fail loudly

#include <iostream>
#include <vector>

int main() {
    std::vector<int> readings;
    int value;
    while (std::cin >> value) {
        readings.push_back(value);
    }
    if (readings.empty()) {
        std::cout << "no readings\n";
        return 0;
    }
    for (std::size_t i = 0; i < readings.size(); ++i) {
        std::cout << readings.at(i) << ' ';
    }
}

Added: an empty check and at(i). An empty vector has no valid index; at throws instead of causing undefined behavior.

Step 5: Nest vectors — rows may differ in length

#include <iostream>
#include <vector>

int main() {
    std::vector<std::vector<int>> scores{{8, 7, 9}, {6, 10}};
    for (const auto& row : scores) {
        int total = 0;
        for (int score : row) {
            total += score;
        }
        std::cout << total << '\n';
    }
}

Replaced: the flat vector with a vector of rows. Each row is traversed with its own size, so a ragged shape is safe.