06 Arrays
A practical guide to declaring, initializing, accessing, traversing, passing, and choosing fixed-size arrays in C++.
1. The model
An is a fixed-size sequence whose elements all have the same type and occupy . This layout allows C++ to locate an element efficiently, but it also means that the program must use valid indices and track the 's size carefully.
For a built-in with elements, the valid index set is:
The number of elements is the 's size; it is not the largest index. Thus, int scores[5] contains five elements with indices , , , , and .
Takeaway: A built-in has a fixed element count, one element type, contiguous storage, and zero-based indices.
2. Declaring one-dimensional arrays
A one-dimensional built-in is declared with a type, a name, and a bound, such as int scores[5], char letters[26], or double prices[10]. For an automatic , the bound must be a constant expression.
The bound specifies how many elements exist. In int values[4], the valid indices are through , so the loop condition for visiting every element is normally i < 4, not i <= 4.
An can contain fundamental types, pointers, class objects, enumerations, or other arrays. The element type determines the type of every item in the sequence.
Takeaway: Read a declaration such as type name[size] as “a fixed number of elements of type,” not as “an element whose index is `size**.”
3. Initializing arrays
assigns starting values to elements. A complete braced initializer fills elements in index order: int numbers[5] = {10, 20, 30, 40, 50} assigns 10 to numbers[0] and 50 to numbers[4].
If the initializer has fewer values than the declared bound, the remaining elements are value-initialized. For example, int values[5] = {7, 8} produces the values 7, 8, 0, 0, and 0.
The bound may be omitted when an initializer is present. In int primes[] = {2, 3, 5, 7, 11}, the compiler deduces an with five elements. An empty initializer such as int counts[4]{} value-initializes every element, producing four zeroes for an integer .
A character can be initialized with a string literal. char word[] = "hello" has six elements because it includes the terminating null character \\0. An explicitly sized declaration therefore needs room for that terminator, as in char word[6] = "hello".
Takeaway: Braced can fill an , zero-initialize its remaining elements, or allow the compiler to deduce the bound.
4. Accessing elements safely
The subscript operator accesses an element: [index]. For a built-in , this operation is defined in terms of pointer arithmetic and dereferencing:
For example, if temperatures contains three values, temperatures[0] refers to the first value and temperatures[2] refers to the last. Assignment through a subscript modifies the selected element.
C++ does not perform automatic bounds checking for built-in arrays. An index below or at least as large as the size accesses outside the valid range and produces . A loop over an of four elements should therefore use an index satisfying .
Takeaway: Subscripts are convenient, but every index must remain within the range from through .
5. Traversing one- and two-dimensional arrays
Traversal means visiting each element, usually with a loop. An index-based for loop is appropriate when the position is needed or when the code must update elements by index.
When the is still an in the current scope, its element count can be computed with sizeof(values) / sizeof(values[0]). This works because sizeof(values) measures the whole and sizeof(values[0]) measures one element. It does not work after an ordinary parameter has been adjusted to a pointer.
A Range-based for loop is often clearer when the index is unnecessary. A loop variable such as int value reads copies of the elements. Use int& value when the loop must modify the original elements, and const int& value when the loop should read without modifying them.
For a two-dimensional , use nested loops. The outer loop selects a row, and the inner loop selects each column in that row.
Takeaway: Choose index-based loops for positions and range-based loops for straightforward element visits; use nested loops for nested arrays.
6. Common operations
Built-in arrays do not provide member functions such as sort, size, or push_back. Common operations are implemented with loops or standard-library algorithms.
To calculate a sum, initialize an accumulator and add each element. To calculate an average, ensure that the division is floating-point rather than integer division. For example, converting the sum to double before dividing by the count preserves the fractional part.
To find a maximum, initialize the candidate from the first element and compare the remaining elements against it. This approach assumes that the is non-empty. To search, compare each element with a target and stop when a match is found. To reverse an , exchange symmetric elements from the two ends while the left position remains before the right position.
The standard library provides algorithms such as std::find, std::max_element, and std::reverse for these tasks.
Takeaway: algorithms must explicitly manage iteration, counts, and special cases such as an empty .
7. Multidimensional arrays
A is an whose elements are arrays. The declaration int matrix[2][3] represents two rows, each containing three integers. The first subscript selects a row and the second selects a column.
Nested braces make the structure clear:
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}}
The same values can be supplied in one initializer sequence. The rightmost index varies fastest, so the order is matrix[0][0], matrix[0][1], matrix[0][2], then matrix[1][0], matrix[1][1], and matrix[1][2].
A nested loop typically uses an outer row index and an inner column index. For a three-dimensional declaration such as int cube[2][3][4], each additional subscript selects another nested level.
Takeaway: A multidimensional built-in is a hierarchy of arrays, and its rightmost dimension changes fastest during traversal and .
8. Passing arrays to functions
When a one-dimensional built-in is passed to a function using ordinary -parameter syntax, the parameter is adjusted to a pointer to the first element. Consequently, these parameter forms are equivalent:
void print(const int values[], int size)
void print(const int* values, int size)
The function does not automatically receive the number of elements, so the caller normally supplies the size separately. This is why the function can iterate safely only when the size argument is correct.
Use const when the function should only read the elements. Without const, the function can modify the caller's original elements because the pointer refers to the same storage. For example, a function that doubles each value changes the seen by its caller.
For a two-dimensional parameter, every dimension except the first generally needs a bound. A parameter such as int matrix[][3] tells the compiler the number of columns needed to calculate element addresses; int matrix[][] is invalid.
Takeaway: Ordinary parameters behave like pointers, so pass the size explicitly and specify the remaining dimensions of multidimensional arrays.
9. Arrays and pointers
An often undergoes in expressions, producing a pointer to its first element. If int values[3] = {10, 20, 30}, then assigning values to an int* gives a pointer to values[0]. Dereferencing that pointer accesses the first element, and adding one moves to the next element of the same .
The itself is not a pointer. This distinction is visible with sizeof: sizeof(values) measures storage for all three elements, while sizeof(pointer) measures the pointer object. The conversion is suppressed in contexts such as sizeof and when an initializes a reference.
Because pointer arithmetic and subscripting rely on the 's layout, moving outside the permitted range causes . Pointer-based code therefore requires the same careful bounds reasoning as index-based code.
Takeaway: Arrays and pointers are closely related in expressions, but they are different types with different behavior under sizeof and reference .
10. Choosing an representation
Choose a built-in when the element count is fixed and low-level compatibility or direct layout matters. Its syntax is fundamental for understanding pointer arithmetic, multidimensional layout, and older interfaces.
Use when the size is fixed but a standard-library type with explicit size information and -like operations is preferable. Use when the number of elements can change during the object's lifetime. Use std::string for text instead of managing a raw character in ordinary string-processing code.
These choices reflect different requirements:
A built-in provides fixed storage and low-level behavior.
<T, N>provides fixed size as part of its type.<T>provides a dynamically sized sequence.std::stringmodels text directly.
Takeaway: The built-in is important for language fundamentals and low-level interfaces, while library types often provide safer and more convenient interfaces for application code.