C++ Arrays
An array in C++ is a fixed-size collection of elements that are all the same type, stored one after another in a single contiguous block of memory. Arrays matter because they give you extremely fast, predictable access to data by position (an index), and they are the foundation on which higher-level containers like std::vector and std::array are built. Understanding raw C++ arrays — how they are laid out in memory, how they decay to pointers, and where they can go wrong — is essential to understanding pointers, strings, and dynamic memory later on.
Overview: How Arrays Work
When you declare an array, the compiler reserves a single contiguous chunk of memory large enough to hold all of its elements. For example, int scores[5]; reserves space for five int values sitting back-to-back in memory. If an int is 4 bytes on your system, the whole array occupies 20 bytes, and the address of scores[i] is always (address of scores[0]) + i * sizeof(int). This is why indexing is so fast: the compiler turns scores[i] into a single pointer-arithmetic calculation, not a search.
Arrays in C++ have a size that must be known at compile time (a constant expression) when declared this way — you cannot write int n; std::cin >> n; int arr[n]; in standard C++ (some compilers allow this as a non-standard extension called a variable-length array, but you should not rely on it). If you need a size decided at runtime, use std::vector instead, which manages memory dynamically on the heap.
Another crucial fact: in most expressions, the name of an array “decays” into a pointer to its first element. This is why arrays can be passed to functions efficiently (no copying of every element), but it also means the function receiving the array loses direct knowledge of how many elements it holds — a common source of bugs covered later in this lesson.
C++ also supports multi-dimensional arrays, such as int grid[3][4];, which are really just arrays of arrays: a contiguous block of 3 sub-arrays, each containing 4 ints, laid out row by row in memory (this is called row-major order).
Syntax
type name[size]; // declaration, elements are uninitialized (garbage)
type name[size] = {v1, v2, ...}; // declaration with initializer list
type name[] = {v1, v2, v3}; // size is inferred from the initializer list
type name[rows][cols]; // two-dimensional array
| Part | Meaning |
|---|---|
type |
The element type — every element must be this same type (e.g. int, double, char). |
name |
The identifier used to refer to the array. |
size |
The number of elements. Must be a constant expression known at compile time. |
{v1, v2, ...} |
An optional initializer list. Any elements not given a value are zero-initialized if at least one value is provided; with no initializer at all, elements hold indeterminate garbage values. |
name[i] |
The subscript operator — accesses the element at zero-based index i. Equivalent to *(name + i). |
Examples
Example 1: Summing and Averaging an Array
#include <iostream>
int main() {
int scores[5] = {88, 92, 79, 95, 84};
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += scores[i];
}
double average = static_cast<double>(sum) / 5;
std::cout << "Scores: ";
for (int i = 0; i < 5; i++) {
std::cout << scores[i] << " ";
}
std::cout << "\n";
std::cout << "Sum: " << sum << "\n";
std::cout << "Average: " << average << "\n";
return 0;
}
Output:
Scores: 88 92 79 95 84
Sum: 438
Average: 87.6
The array scores holds five values initialized directly in the declaration. The first loop reads each element by index and accumulates the total in sum; the second loop prints them back out. Note the cast to double before dividing — without it, sum / 5 would perform integer division and truncate the result.
Example 2: Two-Dimensional Arrays and Range-Based Loops
#include <iostream>
int main() {
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 3; col++) {
std::cout << matrix[row][col] << " ";
}
std::cout << "\n";
}
int temps[4] = {72, 75, 68, 80};
int highest = temps[0];
for (int t : temps) {
if (t > highest) {
highest = t;
}
}
std::cout << "Highest temperature: " << highest << "\n";
return 0;
}
Output:
1 2 3
4 5 6
Highest temperature: 80
The 2D array matrix is really a 2-element array of 3-element arrays, so matrix[row][col] picks the row first, then the column within it. The second part shows a range-based for loop (for (int t : temps)), a C++11 feature that iterates over every element of an array without you having to manage an index manually — ideal when you only need to read values in order.
Example 3: Passing Arrays to Functions
#include <iostream>
double calculateAverage(const int arr[], int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return static_cast<double>(sum) / size;
}
int main() {
int prices[6] = {10, 25, 30, 15, 40, 20};
int size = sizeof(prices) / sizeof(prices[0]);
std::cout << "Number of elements: " << size << "\n";
std::cout << "Average price: " << calculateAverage(prices, size) << "\n";
return 0;
}
Output:
Number of elements: 6
Average price: 23.3333
Inside main, sizeof(prices) / sizeof(prices[0]) correctly computes the element count (total bytes divided by bytes-per-element) because prices is still a true array in this scope. That count is then passed explicitly into calculateAverage as a separate size parameter, because once the array is passed as a function argument, it decays to a plain pointer (const int arr[] is treated identically to const int* arr) and the function has no way to recover the original size on its own.
How It Works Step by Step (Under the Hood)
- Allocation: A locally declared array (e.g. inside
main) is allocated on the stack. Its total size (sizeof(type) * size) is reserved as one contiguous block the moment the enclosing scope is entered. - Indexing: The expression
arr[i]is defined by the language as*(arr + i): take the array’s base address, move forwardielements (the compiler automatically scales bysizeof(type)), and dereference. This is why array indexing has constant-time, O(1), performance. - Decay: Whenever an array is used in most expressions — passed to a function, assigned to a pointer, used in arithmetic — its name automatically converts (“decays”) into a pointer to its first element.
arrand&arr[0]then refer to the same address. The two exceptions where decay does not happen aresizeof(arr)and taking the address of the whole array with&arr. - No bounds checking: The compiler does not generate any code to verify that an index is within range. Reading or writing outside the array’s bounds compiles without error but produces undefined behavior at runtime — it may silently corrupt other data, crash, or appear to work by luck.
- Lifetime: A stack-allocated array is destroyed automatically when its scope ends. Returning a pointer to a local array from a function leaves that pointer dangling, pointing at memory that no longer belongs to your data.
Common Mistakes
Mistake 1: Off-by-one / out-of-bounds indexing. Valid indices for int arr[5] run from 0 to 4. Writing a loop condition like for (int i = 0; i <= 5; i++) reads arr[5], which is one past the end of the array — undefined behavior, since there is no bounds checking to catch it. The fix is to use a strict less-than comparison against the element count:
int arr[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << "\n";
Output:
1 2 3 4 5
Mistake 2: Assuming sizeof works on a function parameter. Because arrays decay to pointers when passed to a function, writing sizeof(arr) / sizeof(arr[0]) inside a function that received the array as a parameter does not give you the element count — it typically evaluates to the size of a pointer (often 8 on a 64-bit system) divided by the element size, producing a small, wrong number. sizeof only reports the true array size when used in the same scope where the array was originally declared, as shown in Example 3, where size is computed in main and passed explicitly to calculateAverage.
Best Practices
- Always initialize arrays at declaration (e.g.
int arr[5] = {0};) to avoid reading indeterminate garbage values. - Keep the size in a named constant (
const int SIZE = 5;) and reuse it everywhere instead of repeating a literal number, so bounds stay consistent if the size changes. - Use range-based for loops (
for (auto x : arr)) when you only need to read elements in order — it removes an entire class of off-by-one bugs. - When an array must be passed to a function, always pass its size alongside it (as in Example 3); never assume the function can recover the size on its own.
- Prefer
std::array<T, N>for fixed-size collections andstd::vector<T>for runtime-sized collections over raw C-style arrays when possible — both track their own size, support bounds-checked access via.at(), and are much safer to pass around. - Never return a pointer or reference to a local (stack-allocated) array from a function — the memory is destroyed when the function returns.
- If an index comes from user input or a calculation, validate it is within
[0, size)before using it to access the array.
Practice Exercises
- Declare an
intarray of 7 numbers representing a week of daily step counts. Write a program that computes and prints the total steps for the week and the single highest daily step count. - Write a function
int findValue(const int arr[], int size, int target)that returns the index oftargetinsidearr, or-1if it is not found. Test it against the array{4, 8, 15, 16, 23, 42}searching for16(expected result:3) and for99(expected result:-1). - Declare a 3×3
intmatrix and fill it with the values 1 through 9, row by row. Using nested loops, print the sum of each row and, separately, the sum of each column. (Hint: for column sums, loop over columns on the outside and rows on the inside.)
Summary
- An array is a fixed-size, contiguous block of memory holding elements of the same type, accessed by a zero-based index.
- The array’s size must be a compile-time constant; use
std::vectorif you need a runtime-determined size. - Indexing (
arr[i]) is fast constant-time pointer arithmetic, but C++ performs no automatic bounds checking — out-of-bounds access is undefined behavior. - Arrays decay to pointers in most expressions, including when passed to functions, so you must pass the element count separately.
- Multi-dimensional arrays are arrays of arrays, stored in row-major order in memory.
- Prefer modern alternatives like
std::arrayandstd::vectorfor safety and convenience, reserving raw arrays for performance-critical or low-level code.
