We will always keep the values in our ArrayList packed together at the start of the array. That means if we have 10, 20, 30 in an ArrayList with m_capacity of 5, the contents of the array will always look like [10, 20, 30, ???, ???]. We will never skip a space in the array by storing the items like [10, ???, 20, 30, ???] .
Keeping the data compacted like this serves two important roles:
It makes it easy to locate elements in the ArrayList. When we want to access the element at index 2 of the ArrayList (the third item), we can just look at m_arr[2]. If we allowed a gap in the array, we would not know that the element at index 2 is actually the third item.
It makes it easy to find the next available location in the array. When m_size is 3, that means we have items at indexes 0-2 (size - 1). The next available index would be 3.
Given this design, we generally will insert items at the end of the ArrayList (similar to how in a vector we generally use push_back() to add items). A basic version of it might look like:
template<typename T>
void ArrayList<T>::insertEnd(const T& newItem) {
m_arr[m_size] = newItem; // place the new item in first unused location
m_size++; // increase size to reflect new item
}
Let us imagine what would happen if insertEnd(40) was called on it. First, insertEnd would copy the new item into the location indicated by m_size (3). Then it will increase m_size to 4, telling us that elements 0-3 of the array are now valid indexes in the ArrayList.