Skip to main content
    Courses/C++/Arrays & Vectors

    Lesson 7 • Intermediate

    Arrays & Vectors

    By the end of this lesson you'll be able to store a whole list of values in one variable, read and change any item by its index, and grow a collection on demand with std::vector<int> — the container you'll reach for in almost every C++ program you write.

    What You'll Learn

    • Declare a C-style array (int a[5]) and read/write items by index
    • Understand 0-based indexing and why the last index is size - 1
    • Know that C-style arrays are fixed-size with NO bounds checking
    • Use std::array as a safer, size-aware fixed array
    • Grow a std::vector with push_back and read it with size, front, back
    • Choose [] vs .at(), iterate with range-for, and build 2D vectors

    💡 Real-World Analogy

    A C-style array is a fixed row of post-office boxes. You decide how many boxes to build, and that number can never change. Each box has a number on it starting at 0, and you reach a box by its number — scores[0], scores[1], and so on. There's no security guard: if you ask for box number 5 on a 5-box wall (boxes 0–4), nobody stops you — you just grab whatever is in the next bit of wall, which is a bug.

    A std::vector is the same wall of boxes, but magic and expandable: you can add a new box on the end any time with push_back, it always knows how many boxes it has with size(), and its .at() door has a guard that refuses an invalid box number. That's why modern C++ reaches for vector first.

    1. C-Style Arrays

    An array stores many values of one type in a single named block. You declare it as type name[count], and you reach each value by its index — an offset that starts at 0. So int a[5] holds five ints at indexes 0, 1, 2, 3, and 4; the last valid index is always count - 1. Two things to burn in now: the size is fixed (you can't grow it), and there is no bounds checking (reading past the end is undefined behaviour, not an error). Read this worked example, run it, then you'll write your own.

    Worked example: a C-style array

    Read every comment, run it, and check the output matches.

    Try it Yourself »
    C++
    #include <iostream>
    using namespace std;
    
    int main() {
        // A C-style array is a FIXED row of boxes, all the same type.
        // int scores[5] => 5 ints, indexes 0,1,2,3,4 (NOT 1..5).
        int scores[5] = {95, 87, 73, 100, 68};
    
        // Indexing reads/writes one box. The index is an OFFSET from the start.
        cout << "First: " << scores[0] << endl;   // First: 95   (offset 0)
        cout << "Last:  " << scores[4] << endl;   // Last:  68   (offset 4 = size-1)
    
        scores[2] = 85;                      
    ...

    Your turn. The program below is almost complete — fill in the three blanks marked ___ using the hints, then run it. Watch the indexes carefully.

    🎯 Your turn: index an array

    Fill in the ___ blanks, then check your output against the expected lines.

    Try it Yourself »
    C++
    #include <iostream>
    using namespace std;
    
    int main() {
        // 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
    
        // 1) Make an int array called "ages" holding 5, 8, 12, 16, 21
        int ages[5] = ___;        // 👉 a list in { } e.g. {5, 8, 12, 16, 21}
    
        // 2) Print the FIRST element (remember: offset 0, not 1)
        cout << "Youngest: " << ages[___] << endl;   // 👉 the first index
    
        // 3) Print the LAST element of a size-5 array
        cout << "Oldest: " << ages[___] << endl;    
    ...

    2. std::array — a Safer Fixed Array

    A C-style array forgets its own length, so you end up passing a separate size around by hand. std::array<type, count> fixes that: it's still fixed-size and lives on the stack like a raw array, but it knows its size() and offers a bounds-checked .at(). You need #include <array> to use it. Reach for it when the count is known up front and never changes.

    Worked example: std::array

    See size() and .at() in action on a fixed-size array.

    Try it Yourself »
    C++
    #include <iostream>
    #include <array>          // needed for std::array
    using namespace std;
    
    int main() {
        // std::array is a SAFER fixed-size array. Size is part of its type:
        //   array<type, count>
        array<int, 4> temps = {18, 21, 24, 19};
    
        // It knows its own size — no separate counter to keep in sync.
        cout << "How many: " << temps.size() << endl;   // How many: 4
    
        // [] works like a raw array (no bounds check).
        cout << "temps[0]: " << temps[0] << endl;       // temps
    ...

    3. std::vector — the One You'll Use Most

    A vector is a resizable array. You add to the end with push_back, ask how big it is with size(), and read items with [] (fast, unchecked) or .at() (checked — it throws if the index is invalid). front() and back() grab the first and last item, and a range-based for visits every element without you managing an index. You need #include <vector>. For almost every list of data, this is the container to use.

    Worked example: std::vector

    push_back, size, [] vs .at(), front/back, and range-for.

    Try it Yourself »
    C++
    #include <iostream>
    #include <vector>         // needed for std::vector
    using namespace std;
    
    int main() {
        // A vector is a RESIZABLE array — the workhorse of modern C++.
        vector<int> numbers = {10, 20, 30};   // start with three values
        vector<string> fruits;                // start empty
    
        // push_back ADDS an element on the end and grows the vector.
        fruits.push_back("Apple");
        fruits.push_back("Banana");
        fruits.push_back("Cherry");
    
        // size() tells you how many ele
    ...

    Now you build one. Start from an empty vector and add items to it, then read it back. Fill in the blanks:

    🎯 Your turn: build a vector

    Use push_back and size() to grow and measure a vector.

    Try it Yourself »
    C++
    #include <iostream>
    #include <vector>
    using namespace std;
    
    int main() {
        // 🎯 YOUR TURN — build a shopping list with a vector.
        vector<string> cart;     // starts empty
    
        // 1) Add "Milk" to the cart
        cart.___("Milk");        // 👉 the method that adds to the end
    
        // 2) Add "Bread" to the cart
        cart.push_back(___);     // 👉 the item, in "double quotes"
    
        // 3) Print how many items are in the cart
        cout << "Items: " << cart.___() << endl;   // 👉 the method that return
    ...

    4. 2D Vectors (a Grid)

    Sometimes data is a grid — a board, a spreadsheet, an image. A vector<vector<int>> is a vector whose elements are themselves vectors, so you index it with two brackets: grid[row][col]. The outer size() gives the number of rows, and grid[row].size() gives the columns in that row. A nested for visits every cell.

    Worked example: a 2D vector

    Index with [row][col] and sum every cell with a nested loop.

    Try it Yourself »
    C++
    #include <iostream>
    #include <vector>
    using namespace std;
    
    int main() {
        // A 2D vector is a vector OF vectors — a grid of rows and columns.
        vector<vector<int>> grid = {
            {1, 2, 3},     // row 0
            {4, 5, 6},     // row 1
            {7, 8, 9}      // row 2
        };
    
        // grid[r][c]: first pick the row, then the column.
        cout << "grid[1][2] = " << grid[1][2] << endl;   // grid[1][2] = 6
    
        // grid.size() = number of rows; grid[r].size() = columns in that row.
        cout << "Ro
    ...

    🔎 Deep Dive: [] vs .at()

    v[i] trusts you completely — it does no bounds checking. If i is out of range you get undefined behaviour: maybe a crash, maybe garbage, maybe a silent corruption that bites you ten lines later. It's the fast path for indexes you've already validated (like a clean for loop).

    v.at(i) does the same read but checks the index first and throws a std::out_of_range exception if it's invalid. Use it whenever the index could be wrong — for example, an index that came from user input. A loud, immediate error beats a mysterious one.

    vector<int> v = {10, 20, 30};
    v[5];     // ⚠️ undefined behaviour — no warning, no error
    v.at(5);  // ✅ throws std::out_of_range — fails loudly and safely

    Common Errors (and the fix)

    • Out-of-bounds access (undefined behaviour): reading a[5] on a size-5 array touches memory you don't own. There's no error message — it just misbehaves. Stay within 0 to size - 1, and use .at() when an index might be wrong.
    • Off-by-one in loops: for (int i = 0; i <= 5; i++) on a size-5 array runs one step too far and touches a[5]. Use i < size (strictly less-than), not <=.
    • Modifying a vector while iterating it: calling push_back or erase inside a range-based for can reallocate the storage and invalidate your loop. Finish the loop first, then change the vector.
    • [] hides mistakes that .at() would catch: v[i] never tells you the index was bad. If a value looks like garbage, swap to v.at(i) temporarily — it'll throw exactly where the bad index is.
    • "error: 'vector' was not declared in this scope": you forgot #include <vector> (or <array> for std::array). Add the header at the top.

    📋 Quick Reference

    TaskCodeNotes
    Declare arrayint a[5] = {1,2,3,4,5};Fixed size, no bounds check
    Declare std::arrayarray<int,3> a = {1,2,3};#include <array>
    Declare vectorvector<int> v = {1,2,3};#include <vector>
    Add to endv.push_back(4);Vector only
    Countv.size()Current element count
    Read (fast)v[i]No bounds check
    Read (safe)v.at(i)Throws if out of range
    First / lastv.front() / v.back()No index maths
    Iteratefor (int x : v) {}Range-based for

    Frequently Asked Questions

    Q: Should I use a C-style array, std::array, or std::vector?

    Reach for std::vector by default — it grows on demand, knows its own size, and is safe to pass around. Use std::array only when the size is fixed and known at compile time and you want stack storage. Use a raw C-style array (int a[5]) mostly for learning and for talking to old C code; in modern C++ you rarely need one.

    Q: What is the difference between v[i] and v.at(i)?

    Both read element i. v[i] does NOT check bounds — if i is out of range you get undefined behaviour (a crash, garbage, or silent corruption). v.at(i) DOES check bounds and throws a std::out_of_range exception instead. Use [] in tight loops where you have already checked the index, and .at() when the index might be invalid.

    Q: Why does my array index start at 0?

    The index is an offset from the start, not a count. The first element sits 0 steps from the beginning, so it is a[0]. An array of size 5 therefore has valid indexes 0, 1, 2, 3, 4 — the last one is size minus 1, never size itself. Writing a[5] on a size-5 array is the classic off-by-one bug.

    Q: Why shouldn't I add or remove elements while looping over a vector?

    Calling push_back or erase can move the vector's storage to a new memory location, which invalidates any references, pointers, or iterators you are using to loop. The fix is to finish iterating first, or collect the changes and apply them after the loop, or loop by index with care.

    Q: Does std::vector slow my program down compared to a raw array?

    For element access, no — v[i] compiles down to the same pointer arithmetic as a raw array. The only cost is when the vector grows and has to reallocate. If you know the final size, call v.reserve(n) up front to do all the allocation once. For the vast majority of programs, vector is both fast and far safer.

    Mini-Challenge: Top Score & Total

    No blanks this time — just a brief and an outline to keep you on track. Build it, run it, and check your output against the example in the comments. Finding the max and the sum of a list is a pattern you'll use constantly.

    🎯 Mini-Challenge: largest value and total

    Loop a vector to find its max and its sum, then print both.

    Try it Yourself »
    C++
    #include <iostream>
    #include <vector>
    using namespace std;
    
    int main() {
        // 🎯 MINI-CHALLENGE: highest score & total
        // 1. Make a vector<int> called "scores" holding {72, 95, 60, 88, 95}.
        // 2. Loop over it to find the LARGEST value (start max at scores[0],
        //    then compare each element and keep the bigger one).
        // 3. Also add every value up into an int "total".
        // 4. Print:  "Top score: X"  then  "Total: Y"
        //
        // Hints: use a range-based for (for (int s : score
    ...

    🎉 Lesson Complete

    • ✅ A C-style array int a[5] is fixed-size, 0-indexed, with no bounds checking
    • ✅ The last valid index is size - 1; i < size in loops avoids off-by-one
    • std::array<type, count> is a safer fixed array that knows its size()
    • std::vector grows with push_back and reports its size()
    • [] is fast but unchecked; .at() checks bounds and throws
    • ✅ Use front/back, range-for, and vector<vector<int>> for grids — and prefer vector by default
    • Next lesson: Pointers & References — unlock direct access to memory

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service