The Array Class .cpp file

The .cpp file   --   the whole thing: array.cpp

Defining a static data member
The first thing in the file is the initialization of the static data member. The arrayCount static data member is set to zero to show that no Array objects have been instantiated yet:
int Array::arrayCount = 0;
Constructors (default)
The constructor is straightforward:
Array::Array(int arraySize) {
   ++arrayCount;                               // count one more object
   size = (arraySize > 0 ? arraySize : 10);    // default size is 10
   arrayPtr = new int[size];                   // create space for array

   for (int i = 0; i < size; i++)
      arrayPtr[i] = 0;                         // initialize array elements
}
Increment arrayCount to count the object being instantiated. The size is either set to the parameter passed in, otherwise an arbitrary size of 10. Space is allocated for an int array of the desired size, and the array elements are all initialized to zero.

It is critical to understand that the memory allocated for the array comes off the heap of memory and is not part of the object. For example, say that main instantiates an Array object x:
   Array x(20);
The object owns the memory for the data members, arrayPtr and size. The memory for the array is not part of the object. A picture of the object x's memory looks like
  +-------------+
  |         __  |        _ _ _ ___________________
  |arrayPtr| -|-------->|0|0|0|    ...          0 |
  |         --  |        - - - -------------------
  |      __     |
  |size |20|    |
  |      --     |
  +-------------+
Copy constructor
The copy constructor looks very similar to the default constructor, except that the parameter is another Array object and the newly allocated int array is set to the size of the parameter's int array and intialized to the parameter's int array elements.
Array::Array(const Array &init) {
   ++arrayCount;                             // count one more object
   size = init.size;                         // size this object
   arrayPtr = new int[size];                 // create space for array

   for (int i = 0; i < size; i++)
      arrayPtr[i] = init.arrayPtr[i];        // copy init into object
}
Destructor
While the compiler will deallocate memory for any data members, C++ does not have garbage collection like in Java, so you must deallocate any memory you (the programmer) allocated that is associated with the object. If you don't, you have a memory leak (memory that was allocated, but never deallocated, and therefore can't be used by your program in the future).

Memory allocation/deallocation rule: IF YOU ALLOCATE IT (use a new), YOU MUST DEALLOCATE IT (delete it)!
Array::~Array() {
   --arrayCount;                         // one fewer objects
   delete [] arrayPtr;                   // reclaim space for array
   arrayPtr = NULL;                      // use nullptr in newer C++ versions
}
Since the destructor is called when the object goes out of scope, the arrayCount is decremented (one less object now exists). The delete causes the memory to be deallocated. Because after you delete, arrayPtr is now the address of memory that is no longer yours, always be sure to set it to nullptr.

The operator= with dynamic memory
When you have dynamic memory associated with an object, all operator= functions look alike. Think   x = y;  
const Array& Array::operator=(const Array& right) {
   if (&right != this) {                    // check for self-assignment
      delete [] arrayPtr;                   // reclaim space
      size = right.size;                    // resize this object
      arrayPtr = new int[size];             // create space for array copy

      for (int i = 0; i < size; i++)
         arrayPtr[i] = right.arrayPtr[i];   // copy array into object
   }

   return *this;                            // enables x = y = z;
}
You're always copying the righthand side of the assignment into the lefthand side so basically you want to:
    Deallocate the old memory of the lefthand side's object int array
    Copy the size of the parameter's object (righthand side of =)
    Allocate new memory for the lefthand side object
    Copy the values of the parameter's object int array into the lefthand's int array

This works great unless some client program had the code   x = x;   If so, you'd deallocate x's int array and crash.

So, first you must check for self assignment (compare the address of the parameter object to the current object). If the parameter (the righthand side object) is NOT the current object (the lefthand side object), then deallocate old memory, allocate new memory, and copy.

All operator= with dynamic memory will have the same format:
const Obj& Obj::operator=(const Obj& right) {
   if (&right != this) {                    // check for self-assignment
      // reclaim space
      // resize this object
      // allocate any needed new memory
      // copy any values from right into current object
   }
   return *this;                            // enables x = y = z;
}
You might think that instead of   (&right != this)  , you could write   (right != *this)  . This would logically work, but is less efficient because instead of just comparing an address, you access the object's memory and potentially do lots more computation.

The operator[]
Say you have instantiated object: Array x(20);
Object x
  +-------------+
  |         __  |        _ _ _ ___________________
  |arrayPtr| -|-------->|0|0|0|    ...          0 |
  |         --  |        - - - -------------------
  |      __     |
  |size |20|    |
  |      --     |
  +-------------+
If you refer to x[5], e.g.,   x[5] = 37;   you expect it to access arrayPtr[5]. In other words, the object acts exactly the same as an int array. You write the overloaded operator[] to make this happen:
int& Array::operator[](int subscript) {
   assert(0 <= subscript && subscript < size);
   return arrayPtr[subscript];               // reference return creates lvalue
}
The assert here, asserts that the bool expression is true, i.e., that the parameter, subscript, is a valid element of the int array. If the assertion fails, the program crashes. In real life, you might use exception handling to handle this more gracefully. If subscript is valid, it returns the int array element.

The return type is int&. It must be a reference type because it's on the lefthand side of an assignment statement (an lvalue). Only references can be on the lefthand side of an assignment operator. The returned value is the address, the reference, of the actual int element, arrayPtr[5].

The operator== and operator!=
The operator== is straightforward. Because of the design of the Array class, if the int arrays are different sizes, it returns false as they can't be equal. If they are the same size, it compares the value in the int array, element by element. If it find any element different, it immediately returns false. If it makes it all the through the arrays, then all the elements are equal, and thus the Array objects are equal.
bool Array::operator==(const Array& right) const {
   if (size != right.size)
      return false;                    // arrays of different sizes

   for (int i = 0; i < size; i++)
      if (arrayPtr[i] != right.arrayPtr[i])
         return false;                 // arrays are not equal
   return true;                        // arrays are equal
}
Since the operator!= is similar to operator== except exactly opposite, you can call the operator== to do the work. The current object is *this, so inside the parens is a call to operator== with right as the parameter. The   !   in front negates whatever value is returned.
bool Array::operator!=(const Array& right) const {
   return !(*this == right);
}
The input/output operators (extraction/insertion), operator<< and operator>> are straightforward, traversing the int array data member and displaying the contents. Note that getArrayCount() is also static since it only acts on a static data member. All objects can use the class function.