Arrays
In C++, an array is a data structure that is used to store multiple values of similar data types in a contiguous memory location. An array is a collection of data belonging to the same datatype and category, stored in contiguous memory locations. The size of the array remains fixed once declared.
Properties:
1️⃣ Fixed Size:
- Compile-Time Size: Arrays in C++ have a fixed size, meaning you must specify the number of elements when you declare an array. This size is determined at compile time.
int myArray[5]; // Array with a fixed size of 5
- Dynamic Allocation: Dynamic arrays (allocated using
new
) can have sizes determines at runtime, but once allocated, their size is fixed.
2️⃣ Contiguous Memory Allocation:
- The elements of an array are stored in contiguous memory locations. This ensures efficient memory access and allows for constant-time access to any element using its index.
3️⃣ Zero-Based Indexing:
- Array indices start from 0. The first element is accessed using index 0, the second with index 1, and so on.
int myArray[3] = {10, 20, 30};
// Access: myArray[0] is 10, myArray[1] is 20, myArray[2] is 30
4️⃣ Homogeneous Elements:
- All elements of an array must be of the same data type.. For example, an array of integers will only store integers, and an array of characters will only store characters.
int numbers[5]; // Array of integers
char characters[10]; // Array of characters
5️⃣ Random Access:
- Arrays support direct access to any element using its index. This enables random access to elements and facilitates efficient search operations.
int value = myArray[2]; // Access the third element
6️⃣ Number of Elements:
- While arrays are contiguous blocks of memory, the size information is not part of the array itself; it is often managed separately.
- The number of elements in array is calculated by dividing the size of whole array (calculated using
sizeof
operator) to the size of any single element of the array (mostly the first element).size of whole array / size of single element of array
- If whole array size = 20, and single element size is 4. then
- 20/4 = 5 elements, means this array consists of 5 elements.
#include <iostream>
int main() {
int array[] = {10, 20, 30, 40, 50};
// Calculate the number of elements in the array
std::cout << "Number of elements in the array: " << sizeof(array) / sizeof(array[0]) << std::endl;
return 0;
}
Output:
Number of elements in the array: 5
7️⃣ Pointer Decay:
- Pointer Behavior: In most expressions, the name of an array decays to a pointer to its first element.
- For example, given
int arr[5];
, usingarr
in an expression implicitly converts it toint*
. - Contexts Where Decay Happens:
- Function arguments: When passing an array to a function, it decays to a pointer.
#include <iostream> void printArray(int* arr, int size) { for (int i = 0; i < size; ++i) { std::cout << arr[i] << " "; } std::cout << std::endl; } int main() { int arr[5] = {1, 2, 3, 4, 5}; printArray(arr, 5); // arr decays to int* when passed to the function return 0; }
- Assignments: When assigning an array to a pointer.
- Pointer arithmetic: When performing pointer arithmetic on the array name.
#include <iostream> int main() { int arr[5] = {1, 2, 3, 4, 5}; int* ptr = arr; // arr decays to int* for (int i = 0; i < 5; ++i) { std::cout << *(ptr + i) << " "; // Access elements using pointer arithmetic } std::cout << std::endl; return 0; }
- Function arguments: When passing an array to a function, it decays to a pointer.
8️⃣ Arrays and Functions:
- Passing to Functions: When passing arrays to functions, only the pointer to the first element is passed. The size information is lost unless explicitly passed as a separate parameter.
void printArray(int* arr, size_t size);
9️⃣ No Bounds Checking:
- Unchecked Access: Arrays do not perform bounds checking. Accessing elements outside the defined range leads to undefined behavior.
int arr[3] = {1, 2, 3};
std::cout << arr[5]; // Undefined behavior
Syntax:
In C++, an array is declared by specifying the data type of its elements followed by the array name and the size of the array in square brackets. The syntax looks like this:
dataType arrayName[arraySize];
For example, to declare an array of integers named myArray
with a size of 5:
int myArray[5];
Here,
int
- type of element to be storedmyArray
- name of the array5
- size of the array
Different ways to declare array.
1️⃣ Standard Declaration:
- The basic and most common way to declare an array:
int myArray[5]; // Declares an array of 5 integers
2️⃣ Declaration with Initialization:
Initialize the array elements at the time of declaration. You can declare and initialize an array in one step. The size is determined by the number of elements provided.
It could be of three types:
- Fully Initialized (With size and Initializer list):
int arr[5] = {1, 2, 3, 4, 5}; // Initializes the array with specific values
- Partially Initialized (With size and incomplete elements initializers (Empty Members)):
- If an array has a size
n
, we can store up ton
number of elements in an array. However, what will happen if we store less thann
number of elements. int arr[5] = {1}; // Initializes the first element to 1, and the rest to 0
- In such cases, the compiler assigns random values to the remaining places. Often times, this random value is simply
0
.
- If an array has a size
- Inferred Size (Without size but Initializer list):
int arr[] = {1, 2, 3}; // Size inferred from the number of initializers
- The compiler infers the size based on the number of elements in the initializer list.
Size Specified Later:
Can we declare an array without specifying its size, then assign values later?
Well you cannot declare a traditional array without specifying its size. The size of a static array must be known at compile time. However, you can achieve the effect of declaring an array without specifying its size initially and then assigning values later by using dynamic arrays or the std::vector
class.
// This method is not possible
int dynamicArray[]; // Declaration without size
dynamicArray[0] = 10;
dynamicArray[1] = 20;
// Alternative
int* array = nullptr;
int size;
std::cout << "Enter the size of the array";
std::cin >> size;
// Dynamically allocate an array of the given size
array = new int[size];
Access Elements in C++ Array
1️⃣ Using Indexing:
The most common way to access elements in an array is by using the index operator []
.
In C++, each element is an array is associated with a number. This number is known as an array index. We can access elements of an array by using those indices.
// syntax to access array elements
array[index];
Example:
#include <iostream>
int main() {
int array[] = {10, 20, 30, 40, 50};
int size = sizeof(array) / sizeof(array[0]);
for (int i = 0; i < size; ++i) {
std::cout << array[i] << " ";
}
std::cout << std::endl;
return 0;
}
10 20 30 40 50
2️⃣ Using Pointers:
Well under the hood, arrays are implemented using pointers. So both have a close relationship.
Pointer Decay
:- When we use an array name in most expressions, it decays to a pointer to the first element of the array.
- The name of an array is not a pointer variable but rather an identifier for the starting address of the array.
- For example:
int arr[5] = {1, 2, 3, 4, 5}; int* ptr = arr; // arr decays to a pointer to the first element
We can access elements in an array using pointer arithmetic *(ptr + i)
.
array[i]
=*(array + i)
, both are equivalent.
Example:
#include <iostream>
int main() {
int array[] = {10, 20, 30, 40, 50};
std::cout <<"array[0] = " << *array<< std::endl;
std::cout <<"array[1] = " << *(array + 1)<< std::endl;
std::cout <<"array[2] = " << *(array + 2)<< std::endl;
std::cout <<"array[3] = " << *(array + 3)<< std::endl;
std::cout <<"array[4] = " << *(array + 4)<< std::endl;
return 0;
}
Output:
array[0] = 10
array[1] = 20
array[2] = 30
array[3] = 40
array[4] = 50
Few Things to Remember:
- The array indices start with
0
. Meaningarray[0]
is the first element stored at index0
. - If the size of an array is
n
, the last element is stored at index(n-1)
. - Elements of an array have consecutive addresses. For example, suppose the starting address of
x[0]
is 2120. Then, the address of the next elementx[1]
will be2124
, the address ofx[2]
will be 2128, and so on. Here, the size of each element is increased by 4. This is because the size ofint
is 4 bytes.
Types of Array On the Basis of Dimension-
1️⃣ One-Dimensional Arrays:
One-dimensional arrays are the most straightforward type, representing a list of elements in a single row or column. They are declared using square brackets and an integer size.
int scores[10]; // Declaring a one-dimensional integer array
2️⃣ Multidimensional Arrays:
Multidimensional arrays extend the concept of one-dimensional arrays by introducing multiple indices or dimensions. The most common type is the two-dimensional array, often used to represent matrices.
int matrix[3][4]; // Declaring a two-dimensional integer array (3x4 matrix)
This declares a 2D array named matrix
with three rows and four columns.
int cube[2][3][4]; // Declares a 3D array with dimensions 2*3*4
- The compiler organizes the memory in a row-major order, meaning that elements in the same row are stored next to each other.
matrix[0][0] | matrix[0][1] | matrix[0][2] | matrix[0][3] |
---------------------------------------------------------
matrix[1][0] | matrix[1][1] | matrix[1][2] | matrix[1][3] |
---------------------------------------------------------
matrix[2][0] | matrix[2][1] | matrix[2][2] | matrix[2][3] |
The elements are stored in a linear fashion, and the memory address of each element is calculated based on the row and column indices. The formula for calculating the memory address of an element matrix[i][j]
is:
address = baseAddress + ( i * numOfColumns + j) * sizeof(elementType);
Here, baseAddress
is the starting memory address of the array, i
is the row index, j
is the column index, numOfColumns
is the number of columns in each row, and sizeof(elementType)
is the size of elements in bytes.
Types of Arrays on the Basis of Size -
1️⃣ Fixed-size Array:
Fixed-size arrays are the most basic type, where the size is declared at the compile-time and remains constant throughout the program execution. They provide a straightforward way to group elements of the same type.
int fixedArray[5]; // Declaration of a fixed-size array with size 5
Advantages:
- Predictable Memory Usage: The memory required for fixed-size arrays is known at compile-time, allowing for efficient memory allocation.
Limitations:
- Inflexible Size: The primary drawback is the inability to change the array size dynamically during runtime.
2️⃣ Dynamic Arrays (using Pointer):
Dynamic arrays, created using pointers and memory allocation, offer flexibility in size adjustment during runtime. This type of array is particularly useful when the size is unknown beforehand or needs to change dynamically.
int* dynamicArray = new int[5]; // Declaration of a dynamic array with initial size of 5
Advantages:
- Dynamic Sizing: Size can be adjusted during runtime, providing flexibility in managing varying amounts of data.
Limitations:
- Manual Memory Management: Requires explicit memory allocation and deallocation, which can lead to memory leaks or segmentation faults if not handled carefully.
3. std::array
from the STL:
Introduced in C++11, std::array
is a container class in the STL that combined the benefits of fixed-size arrays with the safety and functionality of the Standard Template Library.
#include <array>
std::array<int, 5> myStdArray; // Declaration of a std::array with size 5
Advantages:
- Size Information: Provides a built-in
size()
function to retrieve the array's size. - Bounds Checking: Offers safety through runtime bounds checking preventing array index out-of-bounds errors.
- STL compatibility: Integrates seamlessly with other STL components facilitating interoperability.
Limitations:
- Fixed Size: Similar to traditional fixed-size arrays,
std::array
has a fixed size determined at compile-time.
⨳ Passing Arrays to Functions
1️⃣ Passing Arrays as Parameters:
When you pass an array to a function, you are essentially passing the memory address of the first element. When you pass an array to a function without explicitly specifying a method (by reference, by pointer, etc.), it is passed by a pointer to its first element. This is due to the array decay phenomenon.
Array decay phenomenon = refers to the automatic conversion of an array to a pointer to its first element. When an array is used in an expression where a pointer is expected, the array is decayed into a pointer. This phenomenon occurs because arrays are not directly passed by value in C/C++; Instead, only a pointer to the first element of the array is passed. The array decay is a result of C++ treating arrays as pointers in certain situations.
∴ When Array Decay Happens:
① Function Parameters: When you pass an array as a function parameter, the array undergoes decay, and the function receives a pointer to the first element.
void myFunction(int arr[]) { /* ... */ }
② Assignment to a Pointer:
int myArray[] = {1, 2, 3};
int* ptr = myArray; // Array decay happens here
③ Expressions Involving Arrays:
int myArray[] = {1, 2, 3};
int* ptr = myArray + 1; // Array decay happens here
// Here ptr is pointing to 2, the second element because of
// pointer arithmetic.
Here's a simple example of passing an array to a function:
#include <iostream>
void printArray(int arr[], int size) {
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main() {
int myArray[] = {1, 2, 3, 4, 5};
int size = sizeof(myArray) / sizeof(myArray[0]);
printArray(myArray, size);
return 0;
}
Another example:
#include <iostream>
void printArraySize(int arr[]) {
// Size of the array cannot be determined within the function
// because arr is a pointer to the first element, not an array with size information.
// sizeof(arr) will not give the size of the array.
std::cout << "Size of array inside function: " << sizeof(arr) << " bytes\n";
}
int main() {
int myArray[] = {1, 2, 3, 4, 5};
// Calling the function without specifying a method
printArraySize(myArray);
// Size of the array can be determined in the main function
std::cout << "Size of array in main function: " << sizeof(myArray) << " bytes\n";
return 0;
}
In the printArraySize
function, arr
is treated as a pointer, and sizeof(arr)
will not give you the size of the array. This is because array loses its size information when passed to a function, and what you get is essentially a pointer.
The output of the above program look like this:
Size of array inside function: 8 bytes
Size of array in main function: 20 bytes
Here, the size inside the function is determined by the size of a pointer (8 bytes on a 64-bit system), not the size of the original array. In contrast, the size in the main function reflects the actual size of the array.
To work with the size information inside a function, you generally need to pass the size as a separate parameter:
#include <iostream>
void printArraySize(int arr[], int size) {
std::cout << "Size of array inside function: " << size << " elements\n";
}
int main() {
int myArray[] = {1, 2, 3, 4, 5};
// Calling the function with size information
printArraySize(myArray, sizeof(myArray) / sizeof(myArray[0]));
return 0;
}
In this example, the size information is explicitly passed to the function allowing you to work with the size of the array size within the function.
2️⃣ Passing by Reference:
You can pass an array by reference, preserving the size information. This is done by using a reference to the array type.
Syntax:
#include <iostream>
void modifyArrayByReference(int (&arr)[], int size) {
for (int i = 0; i < size; ++i) {
arr[i] *= 2; // Modifying the original array
}
}
int main() {
int myArray[] = {1, 2, 3, 4, 5};
modifyArrayByReference(myArray, 5);
// Printing modified array
for (int i = 0; i < 5; ++i) {
std::cout << myArray[i] << " ";
}
return 0;
}
// Output
2 4 6 8 10
Explanation:
int (&arr)[]
: The function parameter declares a reference to an array.
3️⃣ Passing by Pointer:
When an array is passed to a function, it decays to a pointer to its first element. The size information is lost, so you typically pass the size as an additional argument.
Syntax:
#include <iostream>
void printArray(int* arr, int size) {
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
void modifyArrayByPointer(int* arr, int size) {
for (int i = 0; i < size; ++i) {
arr[i] *= 3; // Modifying the original array
}
}
int main() {
int myArray[] = {1, 2, 3, 4, 5};
int size = sizeof(myArray) / sizeof(myArray[0]);
printArray(myArray, size);
modifyArrayByPointer(myArray, size);
// Printing modified array
for (int i = 0; i < 5; ++i) {
std::cout << myArray[i] << " ";
}
return 0;
}
// Output
1 2 3 4 5
3 6 9 12 15
Explanation:
int* arr
: The function parameter declares a pointer to the first element of the array.- Like passing by reference, changes inside the function affect the original array.
4️⃣ Passing with Iterators:
Syntax:
#include <iostream>
void modifyArrayWithIterators(int* begin, int* end) {
for (int* ptr = begin; ptr != end; ++ptr) {
*ptr += 10; // Modifying the original array
}
}
int main() {
int myArray[] = {1, 2, 3, 4, 5};
modifyArrayWithIterators(myArray, myArray + 5);
// Printing modified array
for (int i = 0; i < 5; ++i) {
std::cout << myArray[i] << " ";
}
return 0;
}
// Output
11 12 13 14 15
Explanation:
- The function takes pointers pointing to the beginning and one-past-the-end of the array.
- This method is useful for generic programming.
⨳ Array of Pointer
An "array of pointers" is an array where each element is a pointer to a certain type. Each pointer can point to different memory locations.
Declaration:
int* arr[10]; // Declares an array of 10 pointers to integers
- Each element in the array can point to different integer variables or dynamically allocated integers.
Example:
#include <iostream>
int main() {
int a = 1, b = 2, c = 3;
int* arr[3]; // Array of 3 pointers to integers
arr[0] = &a; // Pointing to a
arr[1] = &b; // Pointing to b
arr[2] = &c; // Pointing to c
// Accessing values through pointers
for (int i = 0; i < 3; i++) {
std::cout << *arr[i] << " ";
}
std::cout << std::endl;
return 0;
}
// Output
1 2 3
⨳ Pointer to Array
A "pointer to array
" is a single pointer that points to an entire array of a certain type. This pointer points to the first element of the array, but the type of the pointer reflects that it points to an array.
Declaration:
int (*ptr)[10]; // Declares a pointer to an array of 10 integers
- The pointer points to the first element of the array, but it can be used to access any element in the array.
Example:
#include <iostream>
int main() {
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int (*ptr)[10] = &arr; // Pointer to the entire array of 10 integers
// Accessing elements through the pointer
for (int i = 0; i < 10; i++) {
std::cout << (*ptr)[i] << " ";
}
std::cout << std::endl;
return 0;
}
// Output
0 1 2 3 4 5 6 7 8 9
❔ Some might wonder, didn't we create the pointer to array before using the simple syntax int* ptr = array;
?
This is pretty good question, actually int* ptr = array;
creates a pointer to an integer and initializes it to point to the first element of the array array
.
- Pointer to the First Element:
- When you do
int* ptr = arr;
,ptr
points to the first element ofarr
. Here,arr
decays to a pointer to its first element. ptr
is of typeint*
, meaning it points to an integer.- It can be used to access any element of the array via pointer arithmetic, but it does not carry information about the size of the array.
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int* ptr = arr; // ptr points to the first element of arr
ptr
is of typeint*
, pointer to an integer.ptr
points to the first element ofarr
.- You can use
ptr
to iterate over the array elements.
- When you do
- Pointer to an Array:
- If you want a pointer to the entire array, you need to declare it with a different syntax that explicitly denotes that it's pointing to an array of a specific size.
- For a pointer to an array of 10 integers, you declare it as
int (*ptr)[10];
. - When declaring a pointer to an array, it is must to specify the size of the array in the type declaration. This is necessary because the size of the array is part of the type of the pointer.
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int (*ptrToArray)[10] = &arr; // ptrToArray points to the entire array arr
ptrToArray
is of typeint (*)[10]
, pointer to an array of 10 integers.ptrToArray
points to the entire arrayarr
.- You need to dereference the pointer to access array elements.