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.