11.7.1. Array 1: Overloaded operator[ ]

Review

The Array1 example refers to previously presented concepts. Please review the following as needed:

A C++ array "is an ordered collection of variables or elements, each of the same data type, that can be referenced or manipulated by one name." Programmers select specific variables within the array with an index value. C++ arrays are a fundamental data type that are always zero-indexed, meaning that programmers cannot change the array indexing. In contrast, arrays in other programming languages, Pascal, for example, are not zero-indexed, allowing programmers to specify both a lower and an upper index bound. Although we can't change the indexing behavior for fundamental arrays, we can define a new class that does!

The first version of the Array class only stores characters, but versions in subsequent chapters will store all data types. Nevertheless, the class demonstrates how to overload the index operator - operator[]. The index operator, in conjunction with the other class members, allows programmers to:

  1. specify the lower and upper index values of an Array object
  2. treat an Array element as an r-value (an array element may appear on the right side of an assignment operator)
  3. treat an Array element as an l-value (an array element may appear on the left side of an assignment operator)
class Array
{
	private:
		int	lower;			// (a)
		int	upper;			// (b)
		char*	array;			// (c)

	public:
		Array(int l, int u);		// (d)
		~Array() { delete[] array; }	// (e)
		char& operator[](int index);	// (f)
};
Array class. The Array class is a simple container that only stores characters, but it supports flexible indexing.
  1. The lowest valid index value
  2. The greatest valid index value
  3. The character data stored in the array; the constructor allocates the memory on the heap
  4. The user must supply a lower and an upper bound to construct an Array object
  5. The destructor destroys the character array when the Array object is destroyed
  6. The overloaded index operator

Although the Array class allows users to choose indexing schemes that are not zero-indexed, the character array that actually stores the data is nevertheless a fundamental character array that is zero-indexed. Therefore, the Array class must map the logical index values used by the client program into the physical index values necessary to access the elements of a C++ character array.

An Array with logical index values from 0 to 5 and physical indexes of 0 to 5. An Array with logical index values from 5 to 10, but the phyisical indexes are 0 to 5 An Array with logical index values from -3 to 3, but the phyisical indexes are 0 to 6
Array a(0, 5); Array b(5, 10); Array c(-3, 3);
(a)(b)(c)
The relation between logical and physical index values. The pictures are abstract representations of how the Array member variable name array is organized in memory. The top row of index values are the "real" or physical C++ index values, while the bottom row are the logical index values as employed by a client program that uses the Array class. The client program constructs Array objects with the illustrated code.
  1. An Array object whose first valid index is 0, but whose size is the upper index + 1
  2. An Array object valid index range does not include 0
  3. An Array object with an index range that is symmetric about the origin
Using the pictures as a guide, we can develop two different but related formulas:
Array::Array(int l, int u) : lower(l), upper(u)
{
	if (upper < lower)
		throw "upper must be >= lower";

	array = new char[upper - lower + 1];
}
The Array class constructor. The constructor validates that the given lower and upper bounds will create a valid Array object and throws an exception if they do not. The bounds are initialized in the initializer list, and memory is allocated on the heap for the character array.
char& Array::operator[](int index)
{
	if (index < lower || index > upper)
		throw "index out of bounds";

	return array[index - lower];
}
The Array class overloaded index operator. The index operator validates the given index value, throwing an exception if it is out of bounds. If the index argument is within bounds, it maps it to a physical array index and returns a reference to the indexed element. The reference is both a valid r- and l-value.
#include <iostream>
using namespace std;

int main()
{
	Array a(0, 5);

	for (int i = 0; i <= 5; i++)
		a[i] = char(i + 'A');	// l-value

	for (int i = 0; i <= 5; i++)
		cout << a[i] << endl;	// r-value


	Array b(5, 10);

	for (int i = 5; i <= 10; i++)
		b[i] = char(i + 'A');	// l-value

	for (int i = 5; i <= 10; i++)
		cout << b[i] << endl;	// r-value


	Array c(-3, 3);

	for (int i = -3; i <= 3; i++)
		c[i] = char(i + 'A');	// l-value

	for (int i = -3; i <= 3; i++)
		cout << c[i] << endl;	// r-value

	return 0;
}
Testing the Array class. The test code instantiates three Array objects, fills them with character data, and then prints the contents to the console. The code demonstrates how to create Array objects, and how to use the overloaded index operator.