Loading
All articles
Programming 28 min read

C++ Arrays: A Visual, Hands-On Guide

Learn C++ arrays visually: how they sit in memory, why counting starts at 0, how to loop, search and sort them, the out-of-bounds trap, 2D arrays, std::array vs std::vector, real benchmarks, and hands-on exercises with answers.

nbkhope
On this page

Imagine keeping track of the scores of a class of 30 students. With ordinary variables you'd need score1, score2, score3… all the way to score30, and adding them up would take one very long line of code. An array solves this: one name, many values, each in its own numbered box.

Arrays are the oldest and simplest way to store a collection of data, and they're also one of the fastest. In this guide you'll see how they look in memory, write loops that walk through them, learn the one mistake every C++ programmer makes at least once, and finish with exercises to practice on.

One name, many boxes

Here's the difference an array makes:

An array is a row of numbered boxes, side by side in memory. Without an array, five separate variables named score1 to score5 hold 90, 85, 77, 92 and 68, scattered around. With an array, one name, scores, covers five boxes in a row holding the same values. Below each box is its index, 0 to 4, and its memory address, ending in be0, be4, be8, bec and bf0: each one 4 bytes after the previous. Every element is the same size and they sit back to back, so the computer finds scores[i] instantly: start address plus i times 4 bytes. That is also why counting starts at 0: the first element is 0 steps from the start

Three ideas to remember:

  1. Every element has the same type. An array of int holds only ints; an array of double holds only doubles.
  2. Elements are numbered starting from 0. That number is called the index. The first element is scores[0], and the last one in an array of 5 is scores[4].
  3. The elements sit side by side in memory, with no gaps. That's what makes arrays so fast.

Your first array program:

1#include <iostream>
2
3int main() {
4    int scores[5] = {90, 85, 77, 92, 68};
5
6    std::cout << "First score: " << scores[0] << "\n";
7    std::cout << "Last score:  " << scores[4] << "\n";
8
9    scores[2] = 80;  // a retake!
10    std::cout << "Score #3 is now " << scores[2] << "\n";
11}

Sample run:

First score: 90
Last score:  68
Score #3 is now 80

Declaring an array

The general pattern is type name[size];, optionally followed by starting values in braces:

Reading an array declaration: int scores[5] = {90, 85, 77, 92, 68};. The word int is the element type: every box holds an int. The word scores is the name: one name for the whole row. The [5] is the size: a constant, fixed when the program is compiled. The values in braces are the starting values: optional, and missing ones become 0. Leave out the size and the compiler counts the values for you: int scores[] = {90, 85, 77}; has 3 elements

The size has to be known when the program is compiled, so it must be a number or a constant, like const int studentCount = 30;. If you need a size that's only known while the program runs (say, typed in by the user), use std::vector, which we'll meet later.

There are several ways to fill an array when you create it. Here they are side by side, in a real program:

1#include <iostream>
2
3void show(const char* label, const int (&values)[5]) {
4    std::cout << label;
5    for (int value : values) {
6        std::cout << value << " ";
7    }
8    std::cout << "\n";
9}
10
11int main() {
12    int allListed[5] = {1, 2, 3, 4, 5};
13    int partlyListed[5] = {1, 2};
14    int emptyBraces[5] = {};
15    int sizeGuessed[] = {7, 7, 7};
16
17    show("{1, 2, 3, 4, 5} -> ", allListed);
18    show("{1, 2}          -> ", partlyListed);
19    show("{}              -> ", emptyBraces);
20    std::cout << "sizeGuessed has " << std::size(sizeGuessed) << " elements\n";
21}

Sample run:

1{1, 2, 3, 4, 5} -> 1 2 3 4 5 
2{1, 2}          -> 1 2 0 0 0 
3{}              -> 0 0 0 0 0 
4sizeGuessed has 3 elements
You writeWhat you getGood to know
int numbers[5];5 elements with unknown values (inside a function)Whatever bits were left in that memory. Don't read them before assigning!
int numbers[5] = {1, 2, 3, 4, 5};1 2 3 4 5Every value listed
int numbers[5] = {1, 2};1 2 0 0 0Missing values become 0
int numbers[5] = {};0 0 0 0 0The easy way to start from all zeros
int numbers[] = {7, 7, 7};3 elementsThe compiler counts the values
int numbers[2] = {1, 2, 3};Compile errorToo many values for the size

Why counting starts at 0

It seems odd at first, but the index is really a distance: how many elements away from the start of the array you are. The first element is 0 steps away. To find scores[3], the computer takes the address where the array starts and moves 3 × 4 bytes forward (an int is 4 bytes on most computers). One multiplication and one addition, no matter how large the array is.

You can see it for yourself by printing each element's address with &:

1#include <iostream>
2
3int main() {
4    int scores[5] = {90, 85, 77, 92, 68};
5
6    std::cout << "Each int takes " << sizeof(int) << " bytes\n";
7    for (int index = 0; index < 5; index++) {
8        std::cout << "scores[" << index << "] = " << scores[index]
9                  << "  lives at " << &scores[index] << "\n";
10    }
11}

Sample run (your addresses will be different, but always 4 apart):

1Each int takes 4 bytes
2scores[0] = 90  lives at 0xb9d91ffbe0
3scores[1] = 85  lives at 0xb9d91ffbe4
4scores[2] = 77  lives at 0xb9d91ffbe8
5scores[3] = 92  lives at 0xb9d91ffbec
6scores[4] = 68  lives at 0xb9d91ffbf0

The addresses are in hexadecimal: e0, e4, e8, ec, f0 are each 4 apart. Neat, tidy, side by side.

Walking through an array with a loop

Arrays and loops are best friends. A for loop visits each element in turn, so one line of code can work on 5 elements or 5 million. Here's exactly what happens, step by step:

How a for loop walks through an array, as a flowchart and a sample run. It starts with index = 0, then checks index < 5. If yes, it uses scores[index], adds one to index and checks again. If no, it's done. The sample run: index 0, true, prints 90; index 1, true, prints 85; index 2, true, prints 77; index 3, true, prints 92; index 4, true, prints 68; index 5, false, the loop ends. The check runs 6 times but the body only 5: the loop stops the moment index reaches the size

Modern C++ gives you two styles of loop. Use the classic loop when you need the index, and the range-based loop when you only need the values:

1#include <iostream>
2#include <iterator>
3
4int main() {
5    double temperatures[] = {21.5, 23.0, 19.8, 25.1, 24.4, 22.7, 20.9};
6
7    // 1. The classic counting loop: you get the index.
8    for (std::size_t day = 0; day < std::size(temperatures); day++) {
9        std::cout << "Day " << day + 1 << ": " << temperatures[day] << " C\n";
10    }
11
12    // 2. The range-based for loop: you get each value, no index to get wrong.
13    double total = 0;
14    for (double temperature : temperatures) {
15        total += temperature;
16    }
17    std::cout << "Weekly average: " << total / std::size(temperatures) << " C\n";
18}

Sample run:

1Day 1: 21.5 C
2Day 2: 23 C
3Day 3: 19.8 C
4Day 4: 25.1 C
5Day 5: 24.4 C
6Day 6: 22.7 C
7Day 7: 20.9 C
8Weekly average: 22.4857 C

The trap: going out of bounds

Here's the mistake every C++ programmer makes sooner or later. Say your array has 8 elements. The valid indexes are 0 to 7. So what's at scores[8]?

Going out of bounds: index 8 in an 8-element array. The array int scores[8] holds 0, 2, 4, 6, 8, 10, 12 and 14 at indexes 0 to 7. Right after it is someone else's memory, and index 8 lands there, marked with a question mark and exclamation point. No error, no warning at run time: it quietly reads or overwrites whatever lives there. Valid indexes run from 0 to size minus 1. A loop written with less-than-or-equal instead of less-than takes exactly one step too many

C++ trusts you completely. It doesn't check that an index is valid, so scores[8] simply reads or writes whatever happens to be in the memory right after the array. Maybe another variable. Maybe nothing important. Maybe something that crashes your program an hour later, far away from the real bug. The official name for this is undefined behavior: anything can happen.

The classic way to get there is a single character, <= instead of <:

1#include <iostream>
2
3int main() {
4    int scores[8] = {};
5    for (int index = 0; index <= 8; index++) {  // Oops: <= instead of <
6        scores[index] = index * 2;
7    }
8    std::cout << "Done: " << scores[7] << "\n";
9}

The good news: compilers have become pretty good at spotting this. Compiling with warnings on (g++ -Wall -O2), GCC 14 says:

1oob.cpp: In function 'int main()':
2oob.cpp:6:23: warning: iteration 8 invokes undefined behavior [-Waggressive-loop-optimizations]
3    6 |         scores[index] = index * 2;
4      |         ~~~~~~~~~~~~~~^~~~~~~~~~~
5oob.cpp:5:31: note: within this loop
6    5 |     for (int index = 0; index <= 8; index++) {  // Oops: <= instead of <
7      |                         ~~~~~~^~~~

Three habits that keep you safe:

  • Loop with index < size, never index <= size.
  • Get the size from std::size(array) instead of typing the number.
  • Prefer the range-based for (int score : scores) when you don't need the index: it can't go out of bounds.

Everyday recipes

Most array code is one of a handful of patterns: add everything up, find the biggest or smallest, count matches. Here are several at once, in a little grade book with a text bar chart:

1#include <iostream>
2#include <iterator>
3
4int main() {
5    int scores[] = {90, 85, 77, 92, 68, 100, 73, 88};
6    const std::size_t count = std::size(scores);
7
8    int total = 0;
9    int highest = scores[0];
10    int lowest = scores[0];
11    int passing = 0;
12
13    for (int score : scores) {
14        total += score;
15        if (score > highest) highest = score;
16        if (score < lowest) lowest = score;
17        if (score >= 70) passing++;
18    }
19
20    std::cout << "Students: " << count << "\n";
21    std::cout << "Average:  " << static_cast<double>(total) / count << "\n";
22    std::cout << "Highest:  " << highest << "\n";
23    std::cout << "Lowest:   " << lowest << "\n";
24    std::cout << "Passing:  " << passing << " of " << count << "\n";
25
26    // A tiny bar chart, one star for every 10 points
27    for (std::size_t student = 0; student < count; student++) {
28        std::cout << "#" << student + 1 << " ";
29        for (int star = 0; star < scores[student] / 10; star++) std::cout << '*';
30        std::cout << " " << scores[student] << "\n";
31    }
32}

Sample run:

1Students: 8
2Average:  84.125
3Highest:  100
4Lowest:   68
5Passing:  7 of 8
6#1 ********* 90
7#2 ******** 85
8#3 ******* 77
9#4 ********* 92
10#5 ****** 68
11#6 ********** 100
12#7 ******* 73
13#8 ******** 88

Notice static_cast<double>(total) / count: without it, dividing two whole numbers would throw away the .125.

Let the standard library do the work

Those patterns are so common that C++ ships ready-made versions in <algorithm> and <numeric>. They work on plain arrays too, using std::begin and std::end:

1#include <algorithm>
2#include <iostream>
3#include <iterator>
4#include <numeric>
5
6int main() {
7    int scores[] = {90, 85, 77, 92, 68, 100, 73, 88};
8
9    int total = std::accumulate(std::begin(scores), std::end(scores), 0);
10    int highest = *std::max_element(std::begin(scores), std::end(scores));
11    auto passing = std::count_if(std::begin(scores), std::end(scores),
12                                 [](int score) { return score >= 70; });
13
14    std::sort(std::begin(scores), std::end(scores));
15
16    std::cout << "Total: " << total << ", highest: " << highest
17              << ", passing: " << passing << "\nSorted: ";
18    for (int score : scores) std::cout << score << " ";
19    std::cout << "\n";
20}

Sample run:

Total: 673, highest: 100, passing: 7
Sorted: 68 73 77 85 88 90 92 100 
TaskWrite the loop yourselfOr use
Add everything uptotal += valuestd::accumulate
Biggest / smallestcompare and keep the beststd::max_element / std::min_element
Count matchesif (...) count++std::count_if
Find a valuestop when you see itstd::find
Sort(a whole topic of its own!)std::sort
Reverseswap from both endsstd::reverse

Writing the loops yourself is the best way to learn. Using the library is the best way to write code that's short and correct.

A fun one: reversing in place

To reverse an array, swap the first and last elements, then the second and second-to-last, and walk inward until the two ends meet:

1#include <iostream>
2#include <utility>
3
4int main() {
5    char letters[] = {'s', 't', 'r', 'e', 's', 's', 'e', 'd'};
6    const int count = 8;
7    for (int left = 0, right = count - 1; left < right; left++, right--) {
8        std::swap(letters[left], letters[right]);
9    }
10    for (char letter : letters) std::cout << letter;
11    std::cout << "\n";
12}

Sample run:

desserts

"stressed" spelled backwards is "desserts". Only 4 swaps for 8 letters.

Searching: one by one, or by halves?

To find a value in an array, the simplest approach is a linear search: look at every box until you find it. It always works, but on a big array it's a lot of looking.

If the array is sorted, you can do much better with a binary search: look at the middle element. If it's too small, the value can only be in the right half; if it's too big, only in the left half. Each step throws away half of what's left, just like finding a word in a paper dictionary.

1#include <iostream>
2
3int main() {
4    int sorted[] = {3, 8, 15, 21, 34, 42, 57, 63, 71, 88};
5    int target = 57;
6    int low = 0;
7    int high = 9;
8    int step = 0;
9
10    while (low <= high) {
11        int middle = low + (high - low) / 2;
12        step++;
13        std::cout << "Step " << step << ": look at sorted[" << middle << "] = " << sorted[middle];
14        if (sorted[middle] == target) {
15            std::cout << "  -> found it!\n";
16            return 0;
17        }
18        if (sorted[middle] < target) {
19            std::cout << "  -> too small, search the right half\n";
20            low = middle + 1;
21        } else {
22            std::cout << "  -> too big, search the left half\n";
23            high = middle - 1;
24        }
25    }
26    std::cout << "Not found\n";
27}

Sample run:

1Step 1: look at sorted[4] = 34  -> too small, search the right half
2Step 2: look at sorted[7] = 63  -> too big, search the left half
3Step 3: look at sorted[5] = 42  -> too small, search the right half
4Step 4: look at sorted[6] = 57  -> found it!

With 10 elements that's a modest win. With a million, it's dramatic. We counted the steps each search takes in the worst case, when the value isn't there at all:

Searching: checking every box vs. halving a sorted array. Worst case, the value isn't there, steps counted by a C++ program for this article, on a logarithmic scale. 10 elements: linear search 10 steps, binary search 3. 100 elements: 100 vs. 6. 1,000 elements: 1,000 vs. 9. 10,000 elements: 10,000 vs. 13. 100,000 elements: 100,000 vs. 16. 1,000,000 elements: 1,000,000 vs. 19. Make the array 10 times bigger and a linear search does 10 times more work, while a binary search needs only about 3 more steps

Arrays and functions: where the size goes missing

When you pass a plain array to a function, C++ doesn't copy the array. It passes just the address of the first element, a pointer. The function has no idea how long the array is. Programmers call this array decay:

Pass an array to a function and its size gets lost. In main, int numbers[5] holds 1 to 5, and sizeof(numbers) is 20 bytes: 5 elements times 4 bytes each. Inside printSize(int numbers[]), the parameter is really int* numbers, the address of numbers[0], pointing back at the first element. There, sizeof(numbers) is 8 bytes: just the size of a pointer. The function receives only where the array starts, not how long it is. Pass the count along, or use std::array, std::vector or std::span

You can watch it happen:

1#include <iostream>
2
3void printSize(int numbers[]) {
4    std::cout << "Inside the function: " << sizeof(numbers) << " bytes\n";
5}
6
7int main() {
8    int numbers[5] = {1, 2, 3, 4, 5};
9    std::cout << "In main:             " << sizeof(numbers) << " bytes\n";
10    printSize(numbers);
11}

Sample run (on a 64-bit computer):

In main:             20 bytes
Inside the function: 8 bytes

GCC even warns about it: 'sizeof' on array function parameter 'numbers' will return size of 'int*'. The fix is to pass the size along with the array. And since the function received the array's real address, any changes it makes stick:

1#include <iostream>
2#include <iterator>
3
4// Pass the array AND its size: inside, the function only sees a pointer.
5void doubleAll(int numbers[], std::size_t count) {
6    for (std::size_t index = 0; index < count; index++) {
7        numbers[index] *= 2;
8    }
9}
10
11int main() {
12    int numbers[] = {1, 2, 3, 4, 5};
13    doubleAll(numbers, std::size(numbers));
14    for (int number : numbers) std::cout << number << " ";
15    std::cout << "\n";
16}

Sample run:

2 4 6 8 10 

Two-dimensional arrays: grids and tables

An array's elements can be arrays themselves. That gives you a grid, perfect for game boards, spreadsheets, images and maps. You use two indexes: the row first, then the column:

A 2D array is rows of rows, stored one row after another. A 3 by 3 tic-tac-toe board, char board[3][3], with rows 0 to 2 and columns 0 to 2. Row 0 holds X, O, X; row 1 holds blank, X, O; row 2 holds O, blank, X. In memory, it sits as one long row of 9 cells: row 0, then row 1, then row 2. board[1][2], the O at row 1, column 2, is element 1 times 3 plus 2, which is element 5, of the long row
1#include <iostream>
2
3int main() {
4    char board[3][3] = {
5        {'X', 'O', 'X'},
6        {' ', 'X', 'O'},
7        {'O', ' ', 'X'},
8    };
9
10    for (int row = 0; row < 3; row++) {
11        for (int column = 0; column < 3; column++) {
12            std::cout << ' ' << board[row][column] << ' ';
13            if (column < 2) std::cout << '|';
14        }
15        std::cout << "\n";
16        if (row < 2) std::cout << "---+---+---\n";
17    }
18
19    bool diagonalWin = board[0][0] == 'X' && board[1][1] == 'X' && board[2][2] == 'X';
20    std::cout << (diagonalWin ? "X wins on the diagonal!" : "No winner yet") << "\n";
21}

Sample run:

1 X | O | X 
2---+---+---
3   | X | O 
4---+---+---
5 O |   | X 
6X wins on the diagonal!

Two nested loops are the standard way to visit a grid: the outer loop picks a row, the inner loop walks across it.

Why arrays are fast (and how to keep them fast)

Because the elements sit side by side, arrays are incredibly friendly to your computer's processor. Memory isn't fetched one number at a time: it arrives in chunks of 64 bytes, called cache lines. Read scores[0] and the next 15 ints come along for free.

We measured this for the article. We summed all 16 million ints in a 64 MB array, visiting them in three different orders. Same array, same total, same number of additions:

Same array, same sum, different order: up to 19 times slower. Visiting all 16 million ints of a 64 MB array in three orders, measured for this article with GCC 14.1 -O2, median run. In order, 0, 1, 2, 3: 7.0 milliseconds. Jumping by 16, 0, 16, 32, 48: 52.4 milliseconds, 8 times slower. Random, shuffled indexes: 133.0 milliseconds, 19 times slower. Memory is fetched in 64-byte chunks. Walking in order uses all 16 ints in each chunk; jumping by 16 uses just one; random order also defeats the processor's guess of what comes next

Walking in order was 19 times faster than random order, for exactly the same work. The lesson: when you can, go through an array from start to finish. For 2D arrays, that means looping over rows on the outside and columns on the inside, matching how they sit in memory.

Modern C++: std::array and std::vector

The built-in arrays in this article come from C, and they have the rough edges you've seen: no size checks, and the size goes missing when passed to a function. Modern C++ offers two friendlier containers that keep the same side-by-side layout.

std::array is a fixed-size array that knows its own size and offers a checked .at():

1#include <array>
2#include <iostream>
3#include <stdexcept>
4
5int main() {
6    std::array<int, 5> scores = {90, 85, 77, 92, 68};
7
8    std::cout << "Size: " << scores.size() << "\n";
9    std::cout << "Front: " << scores.front() << ", back: " << scores.back() << "\n";
10
11    try {
12        std::cout << scores.at(5) << "\n";  // one past the end
13    } catch (const std::out_of_range& error) {
14        std::cout << "Caught it: " << error.what() << "\n";
15    }
16}

Sample run (the exact message depends on your compiler):

Size: 5
Front: 90, back: 68
Caught it: array::at: __n (which is 5) >= _Nm (which is 5)

Instead of quietly reading someone else's memory, .at(5) stops and reports the problem.

std::vector is an array that can grow and shrink while the program runs:

1#include <iostream>
2#include <vector>
3
4int main() {
5    std::vector<int> scores = {90, 85, 77};
6    scores.push_back(92);   // grows as needed
7    scores.push_back(68);
8
9    std::cout << "Now holding " << scores.size() << " scores: ";
10    for (int score : scores) std::cout << score << " ";
11    std::cout << "\n";
12}

Sample run:

Now holding 5 scores: 90 85 77 92 68 

Do these conveniences cost speed? We measured that too, adding up the same 16 million ints with each one:

The safer choices cost nothing in speed. Adding up 16 million ints, 64 MB, measured for this article with GCC 14.1 -O2, median run, on one Windows laptop. Built-in array int[]: 4.22 milliseconds. std::array, fixed size: 4.38 milliseconds. std::vector, can grow: 3.98 milliseconds. All three store their elements back to back, so the compiler produces the same fast loop. Differences this small are measurement noise

No real difference. All three store their elements back to back, and the compiler turns the loop into the same fast machine code.

Which one should you use?

Which kind of array should you use? First question: will the number of elements change while the program runs? If yes, use std::vector<int>: it grows and shrinks, and it's the everyday default. If no, second question: are you talking to C code, or learning the basics of how memory works? If yes, use a built-in array like int scores[5], the one from this article. If no, use std::array<int, 5>: fixed size, knows its size, and has .at() checks
Built-in arraystd::arraystd::vector
Exampleint scores[5];std::array<int, 5> scores;std::vector<int> scores;
SizeFixed when compiledFixed when compiledCan change while running
Knows its sizeOnly in the same scope (std::size)Always (.size())Always (.size())
Checked accessNo.at().at()
Keeps its size when passed to a functionNo (decays to a pointer)YesYes
Can be copied with =NoYesYes
SpeedFastJust as fastJust as fast

Practice time

Try these before peeking at the answers below. Copy each program into a file, compile it with g++ -std=c++20 -Wall program.cpp, and run it.

Exercise 1: Predict the output (warm-up). Without running it, what does this print?

1int numbers[] = {4, 8, 15, 16, 23, 42};
2std::cout << numbers[1] + numbers[4] << " "
3          << std::size(numbers) << " "
4          << numbers[std::size(numbers) - 1] << "\n";

Exercise 2: Find the bug. This is supposed to add up all five values, but it gives the wrong answer (and sometimes worse). What's wrong?

1int values[5] = {10, 20, 30, 40, 50};
2int total = 0;
3for (int index = 1; index <= 5; index++) {
4    total += values[index];
5}

Exercise 3: Count the evens. Given int numbers[] = {7, 12, 3, 18, 4, 9, 22, 15};, print how many of them are even. (Hint: number % 2 == 0.)

Exercise 4: Rotate left. Shift every element one place to the left, with the first element wrapping around to the end: {1, 2, 3, 4, 5} becomes {2, 3, 4, 5, 1}.

Exercise 5: Runner-up. Find the second largest distinct value in {72, 95, 88, 95, 61, 90}, using a single loop and no sorting. (The answer is not 95!)

Exercise 6: Coffee shop report (challenge). A café records cups sold for 3 days (rows) and 4 shifts per day (columns):

1int sales[3][4] = {
2    {12, 30, 25, 8},
3    {15, 28, 31, 10},
4    {9, 35, 22, 14},
5};

Print the total for each day, then find which shift sold the most across all three days.

Quick quiz

  1. In int data[10];, what's the index of the last element?
  2. True or false: C++ stops your program with an error if you read data[10].
  3. Which line makes all 100 elements start at zero: int data[100]; or int data[100] = {};?
  4. Your array needs to hold however many names the user types in. Which container fits best?
  5. Inside a function void f(int data[]), why doesn't sizeof(data) tell you how many elements there are?

Answers

Exercise 1: 31 6 42. numbers[1] is 8 and numbers[4] is 23, so their sum is 31; there are 6 elements; and the last one, at index 5, is 42.

Exercise 2: Two bugs in one line. The loop starts at 1, so it skips values[0], and it uses <= 5, so it reads values[5], which is out of bounds. It should be for (int index = 0; index < 5; index++), or better, for (int value : values) total += value;.

Exercise 3:

1#include <iostream>
2
3int main() {
4    int numbers[] = {7, 12, 3, 18, 4, 9, 22, 15};
5    int evenCount = 0;
6    for (int number : numbers) {
7        if (number % 2 == 0) evenCount++;
8    }
9    std::cout << "Even numbers: " << evenCount << "\n";
10}
Even numbers: 4

Exercise 4: Save the first element, slide everything left, then put the saved one at the end. Note the loop stops one early, so index + 1 never goes out of bounds.

1#include <iostream>
2#include <iterator>
3
4int main() {
5    int numbers[] = {1, 2, 3, 4, 5};
6    const std::size_t count = std::size(numbers);
7
8    int first = numbers[0];
9    for (std::size_t index = 0; index + 1 < count; index++) {
10        numbers[index] = numbers[index + 1];
11    }
12    numbers[count - 1] = first;
13
14    for (int number : numbers) std::cout << number << " ";
15    std::cout << "\n";
16}
2 3 4 5 1 

(The one-liner version: std::rotate(std::begin(numbers), std::begin(numbers) + 1, std::end(numbers));.)

Exercise 5: Keep track of the two best values seen so far. The score < largest check makes the duplicate 95 not count twice.

1#include <climits>
2#include <iostream>
3
4int main() {
5    int scores[] = {72, 95, 88, 95, 61, 90};
6    int largest = INT_MIN;
7    int secondLargest = INT_MIN;
8
9    for (int score : scores) {
10        if (score > largest) {
11            secondLargest = largest;
12            largest = score;
13        } else if (score > secondLargest && score < largest) {
14            secondLargest = score;
15        }
16    }
17    std::cout << "Largest: " << largest << ", second largest: " << secondLargest << "\n";
18}
Largest: 95, second largest: 90

Exercise 6: Day totals walk across each row; shift totals walk down each column.

1#include <iostream>
2
3int main() {
4    // Cups of coffee sold: 3 days (rows) x 4 shifts (columns)
5    int sales[3][4] = {
6        {12, 30, 25, 8},
7        {15, 28, 31, 10},
8        {9, 35, 22, 14},
9    };
10
11    for (int day = 0; day < 3; day++) {
12        int dayTotal = 0;
13        for (int shift = 0; shift < 4; shift++) dayTotal += sales[day][shift];
14        std::cout << "Day " << day + 1 << " total: " << dayTotal << "\n";
15    }
16
17    int bestShift = 0;
18    int bestShiftTotal = 0;
19    for (int shift = 0; shift < 4; shift++) {
20        int shiftTotal = 0;
21        for (int day = 0; day < 3; day++) shiftTotal += sales[day][shift];
22        if (shiftTotal > bestShiftTotal) {
23            bestShiftTotal = shiftTotal;
24            bestShift = shift;
25        }
26    }
27    std::cout << "Busiest shift: #" << bestShift + 1 << " with " << bestShiftTotal << " cups\n";
28}
1Day 1 total: 75
2Day 2 total: 84
3Day 3 total: 80
4Busiest shift: #2 with 93 cups

Quiz: (1) 9, the size minus one. (2) False: nothing stops you; it's undefined behavior, which is exactly why it's dangerous. (3) int data[100] = {};. (4) std::vector, because its size can grow while the program runs. (5) Because the array decayed into a pointer, so sizeof measures the pointer (usually 8 bytes), not the array.

Cheat sheet

I want to…Write
Declare 10 ints, all zeroint data[10] = {};
Declare with values, size counted for meint data[] = {3, 1, 4};
Read / change one elementdata[2] / data[2] = 7;
Get the number of elementsstd::size(data)
Visit every valuefor (int value : data) { ... }
Visit every indexfor (std::size_t index = 0; index < std::size(data); index++)
Sortstd::sort(std::begin(data), std::end(data));
Pass to a functionvoid use(int data[], std::size_t count)
A 3 × 4 gridint grid[3][4] = {}; then grid[row][column]
Fixed size, saferstd::array<int, 10> data{};
Size that can changestd::vector<int> data; then data.push_back(5);

Wrapping up

An array is a row of same-type values sitting side by side in memory, all under one name. That simple layout explains almost everything about them:

  • Counting starts at 0, because the index is the distance from the start.
  • Loops and arrays go together: index < size, or a range-based for that can't go wrong.
  • There's no safety net: an index past the end silently touches someone else's memory, so watch your < and turn on compiler warnings.
  • A plain array forgets its size when passed to a function, so pass the size too.
  • In-order access is fast, up to 19 times faster than random access in our test.
  • Modern C++ has friendlier options, std::array and std::vector, that are just as fast.

New to C++? Start with What Is the C++ Programming Language?, then Writing your First Program in C++ and How to Create a Variable in C++.

Learning the C++ programming language? How about doing it with One Hour a Day.

Thanks for reading!

Know someone who would enjoy this? Send it their way.

More articles

Read next

Programming 6 min read

C++ std::vector: Size, Capacity, and Safe Growth

Learn how std::vector grows in C++: the difference between size and capacity, when reserve helps, why resize is different, and how growth can invalidate references.

nbkhope
Programming 5 min read

How to Handle Errors in Go: A Hands-On Guide

Learn Go's everyday error pattern with runnable examples: return an error, check it, add context with %w, and recognize a missing file with errors.Is.

nbkhope

Comments

Be kind and specific. Comments with links from new accounts appear after a moderator reviews them.