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:
int score0 = 91;
int score1 = 78;
int score2 = 85;
// ...seven more variablesThis 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.
#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:
#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:
#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.
#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:
#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():
#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:
#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:
#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:
#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.
#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:
#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:
#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
| Feature | Raw Array | std::vector |
|---|---|---|
| Size fixed at compile time | Yes | No |
| Resize at runtime | No | Yes β push_back, pop_back |
| Query number of elements | Manual tracking required | .size() |
| Safe bounds-checked access | None built-in | .at(i) throws on bad index |
| Header required | None | <vector> |
| Pass to function | Decays to pointer, size lost | Pass by reference, size preserved |
| Initialize with values | int 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::stringworks much likestd::vectorand 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 providessort,find,count, and dozens more operations that work directly on vectors.