Skip to content
C++
Language
since C++11
Beginner

Store and Access Multiple Values with Arrays and Vectors

Learn to store lists of values using C++ arrays and std::vector, iterate over them, and avoid the most common beginner mistakes.

By the end of this page, you will be able to declare and use both raw C++ arrays and std::vector, iterate over a collection of values, grow a list at runtime with push_back, and recognize the mistakes that cause programs to crash or produce garbage output.

What and Why

Imagine tracking the scores of ten players in a game. Without collections, you might write:

cpp
int score0 = 91;
int score1 = 78;
int score2 = 85;
// ...seven more variables

This becomes unmanageable immediately. Collections let you store many values under a single name and refer to each one by its index β€” its numeric position in the list.

C++ gives you two primary tools:

  • Raw arrays β€” a fixed-size block of memory whose length you must know at compile time.
  • std::vector β€” a resizable list from the C++ Standard Library that grows and shrinks as needed.

For most new code, you will reach for std::vector. Raw arrays still appear everywhere in older codebases, embedded systems, and C library interfaces, so understanding both matters.

Step by Step

Raw Arrays

A raw array stores a fixed number of values of the same type. You declare it with the type, a name, and the count in square brackets.

cpp
#include <iostream>

int main() {
    int scores[5] = {91, 78, 85, 62, 99};

    std::cout << scores[0] << "\n"; // first element: 91
    std::cout << scores[4] << "\n"; // last element:  99

    return 0;
}

Three rules to memorize now:

  • Indices start at 0, not 1.
  • The last valid index is always size βˆ’ 1. For five elements, that is 4.
  • The size must be a constant known at compile time.

To visit every element, use a loop. The classic form uses the array size as the stopping condition:

cpp
#include <iostream>

int main() {
    int scores[5] = {91, 78, 85, 62, 99};

    for (int i = 0; i < 5; ++i) {
        std::cout << "Score " << i << ": " << scores[i] << "\n";
    }

    return 0;
}

C++11 introduced the range-based for loop, which is cleaner when you do not need the index:

cpp
#include <iostream>

int main() {
    int scores[5] = {91, 78, 85, 62, 99};

    for (int s : scores) {
        std::cout << s << "\n";
    }

    return 0;
}

std::vector

std::vector lives in the <vector> header. It behaves like an array that can grow. You do not need to know the size upfront.

cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> scores;    // empty vector of ints

    scores.push_back(91);       // add elements one by one
    scores.push_back(78);
    scores.push_back(85);

    std::cout << "Count: " << scores.size() << "\n"; // 3
    std::cout << "First: " << scores[0]     << "\n"; // 91

    return 0;
}

You can also initialize a vector with values directly, just like an array:

cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> scores = {91, 78, 85, 62, 99};

    for (int s : scores) {
        std::cout << s << "\n";
    }

    return 0;
}

To remove the last element, call pop_back():

cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> scores = {91, 78, 85};

    scores.pop_back();  // removes 85

    std::cout << "Now has " << scores.size() << " elements\n"; // 2

    return 0;
}

Common Patterns

Building a list from user input

One of the most common uses of std::vector is collecting an unknown number of values at runtime:

cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers;
    int input;

    std::cout << "Enter numbers (0 to stop):\n";
    while (std::cin >> input && input != 0) {
        numbers.push_back(input);
    }

    std::cout << "You entered " << numbers.size() << " numbers.\n";
    return 0;
}

Computing a sum or average

A running total over a range-based loop is a pattern you will use constantly:

cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<double> scores = {88.0, 72.5, 95.0, 61.0, 83.5};

    double total = 0.0;
    for (double s : scores) {
        total += s;
    }

    double average = total / static_cast<double>(scores.size());
    std::cout << "Average: " << average << "\n";
    return 0;
}

Passing a vector to a function

Pass by const reference to avoid copying the entire vector on every call:

cpp
#include <iostream>
#include <vector>

void printAll(const std::vector<int>& items) {
    for (int item : items) {
        std::cout << item << " ";
    }
    std::cout << "\n";
}

int main() {
    std::vector<int> nums = {3, 1, 4, 1, 5, 9};
    printAll(nums);
    return 0;
}

What Can Go Wrong

Out-of-bounds access

Accessing an index that does not exist is undefined behavior β€” your program may crash, produce garbage output, or silently corrupt memory. The compiler will not warn you.

cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {10, 20, 30};

    // BAD: index 3 does not exist; valid indices are 0, 1, 2
    std::cout << v[3] << "\n";   // undefined behavior β€” do not do this

    // GOOD: .at() checks the index and throws std::out_of_range if invalid
    std::cout << v.at(3) << "\n"; // throws, which is safer than silent corruption

    return 0;
}

Use .at(i) while learning and debugging. Once you have verified your index logic is correct, v[i] is fine for performance-sensitive code.

Forgetting that size() is unsigned

vector::size() returns an unsigned integer (std::size_t). Comparing it against a signed int can cause subtle bugs. Prefer either std::size_t as your loop variable or the range-based for loop:

cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3};

    // GOOD: use std::size_t to match the type of .size()
    for (std::size_t i = 0; i < v.size(); ++i) {
        std::cout << v[i] << "\n";
    }

    return 0;
}

When you only need values and not the index, the range-based for loop sidesteps this entirely.

Modifying a vector while iterating over it

Adding or removing elements inside a range-based for loop invalidates the loop's internal state, causing undefined behavior. Collect changes separately and apply them after the loop:

cpp
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3};

    // BAD: push_back inside a range-for is undefined behavior
    // for (int x : v) { v.push_back(x * 2); }

    // GOOD: gather new values first, then append
    std::vector<int> extras;
    for (int x : v) {
        extras.push_back(x * 2);
    }
    for (int x : extras) {
        v.push_back(x);
    }

    return 0;
}

Quick Reference

FeatureRaw Arraystd::vector
Size fixed at compile timeYesNo
Resize at runtimeNoYes β€” push_back, pop_back
Query number of elementsManual tracking required.size()
Safe bounds-checked accessNone built-in.at(i) throws on bad index
Header requiredNone<vector>
Pass to functionDecays to pointer, size lostPass by reference, size preserved
Initialize with valuesint a[3] = {1, 2, 3};vector<int> v = {1, 2, 3};

Rule of thumb: default to std::vector. Use a raw array only when you need a compile-time-fixed buffer or must pass a plain pointer to a C API.

What's Next

  • Strings and Text β€” std::string works much like std::vector and is the standard way to handle text in C++.
  • Functions β€” package your loops and computations into reusable, named blocks.
  • References and Pointers β€” understand exactly what happens when you pass a vector to a function by reference.
  • Range Algorithms β€” the <algorithm> header provides sort, find, count, and dozens more operations that work directly on vectors.