11.7.1. Array 1: Overloaded operator[]

Time: 00:05:13 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides (PDF)
Review

A C++ array is an ordered collection of same-type variables or elements that programs move, reference, or manipulate as a unit with a single name. Programs select specific elements with an index or subscript value. Fundamental or built-in arrays are always zero-indexed, meaning that programmers cannot change the array indexing. In contrast, arrays in other programming languages, such as Pascal, 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 with more flexible indexing.

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() { if (array != nullptr) 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 first or lowest valid index value.
  2. The last or greatest valid index value.
  3. The character data stored in the array; the constructor allocates the memory on the heap. Although implemented as a character pointer, the name lacks a null termination character and, therefore, is not a C-string.
  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. Client programs may use the returned character reference as l- or r-values.

Although the Array class allows users to choose indexing schemes that are not zero-indexed, the character array that 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 in the range [0..5] and physical indexes in [0..5]. An Array with logical index values in the range [5..10] but physical indexes in [0..5]. An Array with logical index values in the range [-3..3] but physical indexes in [0..6].
Array a(0, 5); Array b(5, 10); Array c(-3, 3);
5 - 0 + 1 = 6 10 - 5 + 1 = 6 3 - -3 + 1 = 7
(a)(b)(c)
The relationship between logical and physical index values. Each picture is an abstract representation of an array with the physical indexes shown above the array and the logical indexes below. Based on the Array class in the previous figure, the boxed code illustrates the syntax instantiating Array objects, calling the constructor, and establishing the arrays' lower and upper bounds. The simple arithmetic illustrates how the constructor calculates the array size: upper - lower + 1.
  1. An Array object whose first valid index is 0 and whose size is the upper bound + 1
  2. An Array object with valid indexes in the range [5..10]
  3. An Array object with valid indexes in the range [-3..3]
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 client's lower and upper bounds are meaningful, throwing an exception when they are not. It initializes the bounds in the initializer list, calculates the array's size with the formula described above, and allocates the array's memory on the heap.
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 index parameter, 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. Any member function can map a logical index to a physical index with the expression: index - lower, where index is a function parameter.
#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 + 'D');	// 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 prints the contents to the console. The code demonstrates creating Array objects and using the overloaded index operator.