Vector Arrays in C++ must be pre-allocated to accomodate the maximum number of elements. Should an application attempt to add more than this number, the program may abort, or alter storage of adjacent variables in memory. Let's encapsulate arrays in a Vector class. This class will preallocate a fixed number of elements, but grow as needed to accomodate increasing demands. In addition we'll provide range checking and throw an exception if subscripting exceeds the bounds of the array. To further encapsulate the array, we'll implement an iterator for sequential access. Vector Class class Vector { typedef unsigned int size_type; // default constructor Vector() { vcapacity = 100; vsize = 0; // destructor ~Vector() { delete [] v; // return size of vector int size() const { return vsize; // returns capacity of vector without reallocation int capacity() const { return vcapacity; // subscripting operators double& operator[](size_type i) { return v[i]; // subscripting operators with range checking double& at(size_type i) { if (i >= vsize) throw logic_error("out of range"); return v[i]; size_type vsize; // number of element in vector size_type vcapacity; // capacity of vector without reallocation double *v; // pointer to first element of vector ; // usage: Vector a; Vector::size_type i; // "i" is actually an unsigned int for (i = 0; i < a.size(); i++) cout << a[i]; a[0] = 5; cout << a.at(0); // modify element a[0] // output a[0] with range checking
The Vector class assumes we are storing doubles in the array. Private member v is a pointer to the array, vsize indicates the number of elements actually stored in the vector, and vcapacity the number of available elements. The constructor allocates 100 elements by default, and the subscripting operators allow access to each element. The subscripting operators, [ ] and at, assume that the element being addressed already exists. To add and delete elements from the vector we'll include two new member functions: push_back and pop_back. // add a new element to vector void Vector::push_back(double x) { if (vsize >= vcapacity) { // reallocate vector vcapacity *= 2; double *t = new double[vcapacity]; t[i] = v[i]; delete [] v; v = t; v[vsize++] = x; // delete last element in a vector void Vector::pop_back() { vsize--; Function push_back adds a new element at the end of the vector. First it checks to see if there's enough room in the vector. If not, then it allocates another vector, twice the capacity as the original, copies elements to the new vector, then deletes the original. Function pop_back simply decrements the size of the vector. No error checking is done. To complete functionality, a copy constructor and assignment operator are included. // copy constructor Vector::Vector(const Vector& a) { vcapacity = a.vcapacity; vsize = a.vsize; v[i] = a.v[i]; // assignment operator Vector& Vector::operator=(const Vector& rhs) { if (this == &rhs) return *this; delete [] v; vcapacity = rhs.vcapacity; vsize = rhs.vsize; v[i] = rhs.v[i]; return *this;
Vector Iterator An iterator is an object that allows sequential access to a class's data. We will implement the following iterator: Vector::iterator i; for (i = v.begin(); i!= v.end(); ++i) It helps if you view iterators as pointers to data in the class. In other words, we could rewrite the above statements, using a pointer, as follows: double *i; for (i = &v[0]; i!= &v[n]; ++i) To support this notion, let's define the following begin and end member functions: typedef VectorIterator iterator; // iterator to first item Vector::iterator Vector::begin() const { return v; // iterator to one past last item Vector::iterator Vector::end() const { return v + vsize; We'll implement class VectorIterator to define the iterator itself. To support the return values of begin and end we'll include a constructor in the VectorIterator that converts "double *" to a VectorIterator. class VectorIterator { // constructor VectorIterator() { p = 0; // convert pointer to iterator (used for begin/end) VectorIterator(double *v) { p = v; double *p; ; // pointer to element associated with iterator
Let's repeat the original examples that uses an iterator, Vector::iterator i; for (i = v.begin(); i!= v.end(); ++i) and the corresponding example that uses pointers: double *i; for (i = &v[0]; i!= &v[n]; ++i) We've defined a begin member function that returns &v[0], and an end member function that returns &v[n]. To assign one iterator to another, such as in the statement i = v.begin(); we'll rely on the default bitwise copy behavior of the assignment operator. To compare, increment, and dereference iterators, we'll add the following member functions to VectorIterator: class VectorIterator { // compare iterators bool operator!=(vector::iterator i) const { return i.p!= p; // preincrement operator VectorIterator& operator++() { p = p + 1; return *this; // return value associated with iterator double& operator*() const { return *p; double *p; // pointer to element associated with iterator ;
Finally we'll add an erase member function to the Vector class. This will allow us to delete any item in the vector using an iterator. e.g., Vector a; a.push_back(1); a.push_back(3); a.push_back(5); a.erase(a.begin()); // delete element "1" Code for the erase function follows. We currently don't have a member function that converts an iterator to a pointer, so we'll use the unusual combination of &* to obtain a reference. // delete element designated by iterator Vector::iterator Vector::erase(Vector::iterator i) { // compute subscript of item to erase double *p = &*i; int ss = (int)(p - v); // shift all elements above ss down one notch vsize--; for (size_type j = ss; j < vsize; j++) v[j] = v[j+1]; // return pointer to successor return p;