6 Arrays and Collections

A practical guide to representing, traversing, searching, aggregating, and passing fixed-size collections in C++, including multidimensional arrays and std::array.

The model

A built-in stores a fixed number of values of one type in contiguous memory. For an with NN elements, valid positions range from 00 through N−1N-1. The size cannot change after the declaration.

The declaration pattern is type name[size]. For example, an integer declared with a bound of 44 has four elements: positions 00, 11, 22, and 33. The subscript expression a[i] is defined through pointer arithmetic as ∗(a+i)\ast(a+i), which explains why subscripting works with both arrays and pointers.

C++ does not automatically perform bounds checking. Using an outside the valid range can therefore produce . A loop should normally use a condition such as i<Ni < N, not i≤Ni \leq N.

Takeaway: The size and element type are fixed, indexing begins at 00, and the last valid is always one less than the number of elements.

Declaring and initializing arrays

Brace initialization assigns values from left to right, beginning at 00. If fewer initializers are supplied than the contains, the remaining elements are value-initialized. For an integer , this means the omitted elements become zero.

An empty initializer such as int counts[5]{} initializes every element to zero. When an initializer is present, the bound can be omitted because C++ deduces the size from the number of initializers. A character initialized from a string literal includes a terminating null character when there is room for it.

Before reading an automatic-storage , initialize it explicitly. Declaring an without an initializer does not guarantee that its elements contain useful values.

Example: An initialized with two integer values but declared to hold five elements contains those two values followed by three value-initialized elements.

Takeaway: Initialization determines the starting contents and can also allow C++ to infer the 's bound.

Traversing and modifying elements

visits each element, commonly with an -based for loop or a . An -based loop is useful when the position matters, such as when an algorithm must report or update a particular . When the is available in the same scope, its element count can be computed from its total size and the size of one element.

A is often clearer when only the values matter. Iterate by reference when each element must be modified; iterate by const reference when the elements should be read without copying and must not be changed.

For example, multiplying each element by a constant requires a non-const reference. Printing each element can use a value or a const reference, depending on whether copying is acceptable.

Takeaway: Choose -based when positions matter and range-based when direct element access is enough.

Multidimensional arrays

A multidimensional is an whose elements are themselves arrays. A two-dimensional declaration such as int matrix[2][3] represents two rows and three columns. The first subscript selects a row, and the second selects a column.

The elements use : the complete first row is stored before the complete second row. This layout follows directly from the fact that a two-dimensional is an of one-dimensional arrays.

Nested loops are the usual way to visit every element. The outer loop controls rows, and the inner loop controls columns. Higher-dimensional arrays follow the same nesting pattern. When a multidimensional is initialized, only the first bound may be omitted; the remaining bounds are needed to determine the shape of each nested element.

Takeaway: Keep row and column bounds explicit, and use one loop per dimension to avoid confusing the two indexes.

Searching and aggregating

A checks elements from the beginning toward the end until it finds the target or exhausts the . If the search stops when it finds a match, it returns the first matching position. To report every match, continue the instead of stopping at the first one.

The worst-case running time of a is O(n)O(n), because it may inspect all nn elements. A sorted can support a faster binary search, but the ordering must be maintained and the algorithm requires more structure than the introductory sequential approach.

uses a to combine values. A sum starts at zero and adds each element. An average divides the sum by the element count, but the division should be performed in a floating-point type when a fractional result is needed. For a minimum or maximum, initialize the candidate from the first element rather than from an assumed bound; this works correctly for negative and positive values alike.

Takeaway: Searching selects values or positions, while combines values into a summary.

Passing arrays to functions

When a built-in is passed to a function, its parameter is adjusted to a pointer parameter. The function therefore receives access to the first element but does not automatically receive the original element count. Pass the count separately, and use const when the function must not modify the elements.

For a two-dimensional , every bound except the first must be specified in the parameter type. The column bound is required so the compiler can calculate the address of an element in a selected row.

A reference parameter can preserve the complete type, including its size. With a template parameter for the bound, the compiler can deduce the size from the supplied by the caller, avoiding a separate count argument.

Takeaway: Function interfaces must make size available explicitly, either through a count, a preserved reference type, or a standard container.

Choosing a collection and avoiding errors

A built-in is simple and efficient, but it does not provide member functions such as .size() and cannot be copied or assigned as an ordinary object. For a fixed-size collection, <T, N> provides a container interface while retaining contiguous storage and a compile-time size.

Use when the number of elements is fixed but convenient operations and standard-container behavior are useful. Use a dynamically sized container such as std::vector when the number of elements must change during execution.

Common mistakes include using a loop condition that reaches one past the last , applying sizeof to an parameter and expecting the original size, reading uninitialized elements, and reversing the row and column limits of a matrix.

Takeaway: Match the collection type to the required size behavior, and make bounds and initialization deliberate.