The simplest STL container is the vector, which is a data structure with contiguous blocks of memory just like an array. Because memory locations are contiguous, they can be randomly accessed so that the access time of any element of the vector is con- stant. Storage is managed automatically so that on an attempt to insert an element into a full vector, a larger memory block is allocated for the vector, the vector ele- ments are copied to the new block, and the old block is released. A vector is thus a flexible array; that is, an array whose size can be dynamically changed.
Figure 1.3 alphabetically lists all the vector member functions. An application of these functions is illustrated in Figure 1.4. The contents of affected vectors are shown as comments on the line in which member functions are called. The contents of a vector are output with the generic function printVector(), but in the program in Figure 1.4 only one call is shown.
Figure 1.3 An alphabetical list of member functions in the class vector.
Member Function Operation
void assign(iterator first, Remove all the elements in the vector and insert in it the
iterator last) elements from the range indicated by iterators first and
last.
void assign(size_type n, Remove all the elements in the vector and insert in it n
const T& el = T()) copies of el.
T& at(size_type n) Return the element in position n of the vector.
const T& at(size_type n) const Return the element in position n of the vector.
T& back() Return the last element of the vector.
const T& back() const Return the last element of the vector.
iterator begin() Return an iterator that references the first element of the vector. const_iterator begin() const Return an iterator that references the first element of the vector. size_type capacity() const Return the number of elements that can be stored in the vector.
void clear() Remove all the elements in the vector.
bool empty() const Return true if the vector includes no element and false
otherwise.
iterator end() Return an iterator that is past the last element of the vector.
const_iterator end() const Return a const iterator that is past the last element of the vector.
iterator erase(iterator i) Remove the element referenced by iterator i and return an iterator referencing the element after the one removed.
S e c t i o n 1 . 8 V e c t o r s i n t h e S t a n d a r d T e m p l a t e L i b r a r y ■ 29
Figure 1.3 (continued)
iterator erase(iterator first, Remove the elements in the range indicated by iterators
iterator last) first and last and return an iterator referencing the ele-
ment after the last one removed.
T& front() Return the first element of the vector.
const T& front() const Return the first element of the vector.
iterator insert(iterator i, Insert el before the element referenced by iterator i and
const T& el = T()) return iterator referencing the newly inserted element.
void insert(iterator i, Insert n copies of el before the element referenced by
size_type n, const T& el) iterator i.
void insert(iterator i, Insert elements from the range indicated by iterators iterator first, iterator last) first and last before the element referenced by iterator i.
size_type max_size() const Return the maximum number of elements for the vector.
T& operator[] Subscript operator.
const T& operator[] const Subscript operator.
void pop_back() Remove the last element of the vector.
void push_back(const T& el) Insert el at the end of the vector.
reverse_iterator rbegin() Return an iterator that references the last element of the vector. const_reverse_iterator rbegin() Return an iterator that references the last element of the
const vector.
reverse_iterator rend() Return an iterator that is before the first element of the vector. const_reverse_iterator rend() Return an iterator that is before the first element of the
const vector.
void reserve(size_type n) Reserve enough room for the vector to hold n items if its capacity is less than n.
void resize(size_type n, Make the vector able to hold n elements by adding n -
const T& el = T()) size() more positions with element el or by discarding
over flowing size() - n positions from the end of the vector.
size_type size() const Return the number of elements in the vector.
void swap(vector<T>& v) Swap the content of the vector with the content of another vector v.
vector() Construct an empty vector.
vector(size_type n, Construct a vector with n copies of el of type T (if el is
const T& el = T()) not provided, a default constructor T() is used).
vector(iterator first, Construct a vector with the elements from the range indi-
iterator last) cated by iterators first and last.
Figure 1.4 A program demonstrating the operation of vector member functions.
#include <iostream> #include <vector> #include <algorithm>
#include <functional> // greater<T> using namespace std;
template<class T>
void printVector(char *s, const vector<T>& v) { cout << s << " = (";
if (v.size() == 0) { cout << ")\n"; return;
}
typename vector<T>::const_iterator i = v.begin(); for( ; i != v.end()–1; i++)
cout << *i << ' '; cout << *i << ")\n"; } bool f1(int n) { return n < 4; } int main() { int a[] = {1,2,3,4,5};
vector<int> v1; // v1 is empty, size = 0, capacity = 0 printVector("v1",v1);
for (int j = 1; j <= 5; j++)
v1.push_back(j); // v1 = (1 2 3 4 5), size = 5, capacity = 8 vector<int> v2(3,7); // v2 = (7 7 7)
vector<int> ::iterator i1 = v1.begin()+1;
vector<int> v3(i1,i1+2); // v3 = (2 3), size = 2, capacity = 2 vector<int> v4(v1); // v4 = (1 2 3 4 5), size = 5, capacity = 5 vector<int> v5(5); // v5 = (0 0 0 0 0)
v5[1] = v5.at(3) = 9;// v5 = (0 9 0 9 0)
v3.reserve(6); // v3 = (2 3), size = 2, capacity = 6
v4.resize(7); // v4 = (1 2 3 4 5 0 0), size = 7, capacity = 10 v4.resize(3); // v4 = (1 2 3), size = 3, capacity = 10
v4.clear(); // v4 is empty, size = 0, capacity = 10 (!)
v4.insert(v4.end(),v3[1]); // v4 = (3)
S e c t i o n 1 . 8 V e c t o r s i n t h e S t a n d a r d T e m p l a t e L i b r a r y ■ 31
To use the class vector, the program has to contain the include instruction #include <vector>
The class vector has four constructors. The declaration vector<int> v5(5);
uses the same constructor as declaration vector<int> v2(3,7);
but for vector v5, the element with which it is filled is determined by the default inte- ger constructor, which is zero.
Vector v1 is declared empty and then new elements are inserted with the function push_back(). Adding a new element to the vector is usually fast unless the vector is full and has to be copied to a new block. This situation occurs if the size of the vector equals its capacity. But if the vector has some unused cells, it can accommodate a new element immediately in constant time. The current values of the parameters can be tested with the function size(), which returns the num- ber of elements currently in the vector, and the function capacity(), which Figure 1.4 (continued) v4.insert(v4.end(),2,4); // v4 = (3 3 4 4) v4.insert(v4.end(),v1.begin()+1,v1.end()-1); // v4 = (3 3 4 4 2 3 4) v4.erase(v4.end()-2); // v4 = (3 3 4 4 2 4) v4.erase(v4.begin(), v4.begin()+4); // v4 = (2 4) v4.assign(3,8); // v4 = (8 8 8) v4.assign(a,a+3); // v4 = (1 2 3) vector<int>::reverse_iterator i3 = v4.rbegin(); for ( ; i3 != v4.rend(); i3++)
cout << *i3 << ' '; // print: 3 2 1
cout << endl; // algorithms v5[0] = 3; // v5 = (3 9 0 9 0) replace_if(v5.begin(),v5.end(),f1,7); // v5 = (7 9 7 9 7) v5[0] = 3; v5[2] = v5[4] = 0; // v5 = (3 9 0 9 0) replace(v5.begin(),v5.end(),0,7); // v5 = (3 9 7 9 7) sort(v5.begin(),v5.end()); // v5 = (3 7 7 9 9) sort(v5.begin(),v5.end(),greater<int> ()); // v5 = (9 9 7 7 3) v5.front() = 2; // v5 = (2 9 7 7 3) return 0; }
returns the number of available cells in the vector. If necessary, the capacity can be changed with the function reserve(). For example, after executing
v3.reserve(6);
vector v3 = (2 3) retains the same elements and the same size = 2, but its capac- ity changes from 2 to 6. The function reserve() affects only the capacity of the vector, not its content. The function resize() affects contents and possibly the capacity. For example, vector v4 = (1 2 3 4 5) of size = capacity = 5 changes after execution of
v4.resize(7);
to v4 = (1 2 3 4 5 0 0), size = 7, capacity = 10, and after another call to resize(), v4.resize(3);
to v4 = (1 2 3), size = 3, capacity = 10. These examples indicate that new space is al- located for vectors, but it is not returned.
Note that there is no member function push_front(). This reflects the fact that adding a new element in front of the vector is a complex operation because it requires that all of the elements are moved by one position to make room for the new element. It is thus a time-consuming operation and can be accomplished with the function insert(). This is also a function that automatically allocates more memory for the vector if necessary. Other functions that perform this task are the constructors, the function reserve(), and operator=.
Vector elements can be accessed with the subscript notation used for arrays, as in
v4[0] = n;
or with iterators with the dereferencing notation used for pointers, as in vector<int>::iterator i4 = v4.begin();
*i4 = n;
Note that some member functions have T& return type (that is, a reference type). For example, for an integer vector, the signature of member function front() is
int& front();
This means that, for example, front() can be used on both the left side and the right side of the assignment operator:
v5.front() = 2; v4[1] = v5.front();
All the STL algorithms can be applied to vectors. For example, the call replace(v5.begin(),v5.end(),0,7);
replaces all 0s with 7s in vector v5 so that v5 = (3 9 0 9 0) turns into v5 = (3 9 7 9 7), and the call
S e c t i o n 1 . 8 V e c t o r s i n t h e S t a n d a r d T e m p l a t e L i b r a r y ■ 33
sorts vector v5 in ascending order. Some algorithms allow the use of functional pa rameters. For example, if the program included the definition of function
bool f1(int n) { return n < 4; }
then the call
replace_if(v5.begin(),v5.end(),f1,7);
applies f1() to all elements of v5 and replaces all elements less than 4 with 7. In this case, v5 = (3 9 0 9 0) turns into v5 = (7 9 7 9 7). Incidentally, a more cryptic way to accomplish the same outcome without the need of explicitly defining f1 is given by
replace_if(v5.begin(),v5.end(),bind2nd(less<int>(),4),7); In this expression bind2nd(op,a) is a generic function that behaves as though it converted a two-argument function object into a one-argument function object by supplying (binding) the second parameter. It does this by creating two-argument func- tion objects in which a two-argument operation op takes a as the second argument.
The sorting algorithms allow for the same flexibility. In the example of sort- ing vector v5, v5 is sorted in ascending order. How can we sort it in descending or- der? One way is to sort it in ascending order and then reverse with the algorithm reverse(). Another way is to force sort() to apply the operator > in making its decisions. This is done directly by using a function object as a parameter, as in
sort(v5.begin(),v5.end(),greater<int>()); or indirectly, as in
sort(v5.begin(),v5.end(),f2); where f2 is defined as
bool f2(int m, int n) { return m > n; }
The first method is preferable, but this is possible only because the function object greater is already defined in the STL. This function object is defined as a tem- plate structure, which in essence generically overloads the operator >. Therefore, greater<int>() means that the operator should be applied to integers.
This version of the algorithm sort(), which takes a functional argument, is particularly useful when we need to sort objects more complex than integers and we need to use different criteria. Consider the following class definition:
class Person { public: Person(char *n = "", int a = 0) { name = strdup(n); age = a; }
~Person() { free(name); }
bool operator==(const Person& p) const {
return strcmp(name,p.name) == 0 && age == p.age; }
bool operator<(const Person& p) const { return strcmp(name,p.name) < 0; }
bool operator>(const Person& p) const { return !(*this == p) && !(*this < p); }
private:
char *name; int age;
friend bool lesserAge(const Person&, const Person&); };
Now, with the declaration
vector<Person> v6(1,Person("Gregg",25)); adding to v6 two more objects
v6.push_back(Person("Ann",30)); v6.push_back(Person("Bill",20)); and executing
sort(v6.begin(),v6.end());
v6 changes from v6 = ((“Gregg”, 25) (“Ann”, 30) (“Bill”, 20)) to v6 = ((“Ann”, 30) (“Bill”, 20) (“Gregg”, 25)) sorted in ascending order because the version of sort() with only two iterator arguments uses the operator < overloaded in class Person. The call
sort(v6.begin(),v6.end(),greater<Person>());
changes v6 = ((“Ann”, 30) (“Bill”, 20) (“Gregg”, 25)) to v6 = ((“Gregg”, 25) (“Bill”, 20) (“Ann”, 30)) sorted in descending order because this version of sort() relies on the operator > overloaded for this class. What do we need to do to sort the objects by age? In this case, a function needs to be defined, as in
bool lesserAge(const Person& p1, const Person& p2) { return p1.age < p2.age;
}
and then used as an argument in the call to sort(), sort(v6.begin(),v6.end(),lesserAge);
which causes v6 = ((“Gregg”, 25) (“Bill”, 20) (“Ann”, 30)) to change to v6 = ((“Bill”, 20) (“Gregg”, 25) (“Ann”, 30)).
S e c t i o n 1 . 1 0 C a s e S t u d y : R a n d o m A c c e s s F i l e ■ 35