float * ptr;
p++;
p = p + sizeof(float);
This means that incrementing the pointer is only meaningful as an iterator over arrays.
class iterator { public: iterator (node *p=0) {ptr=p}; iterator operator++(int); char * operator*() {return(ptr->value);}; node * get_ptr() { return(ptr);}; bool not_null() { return(ptr != 0);}; private: node *ptr; }; iterator::operator++(int) { if (ptr != 0) ptr=ptr->next; return *this; }
Why not just overload the ++ and * operators on the list class rather than create a whole class? Functionally, there isn't much difference. Putting the operators in the list class, kind of merges two kinds of objects. The list class encapsulates all the information we have about the list in one place. Including the iterator operations in there mushes the two concepts. Creating an iterator class encapsulates what we know about the pointers in its own class. This is a little vague. The usefulness of a separate iterator class should become clearer when we talk about iterator adaptor classes. The key feature of an iterator over a simple pointer is the ability to assign arbitrary code to the operators * and ++. Again, this will be clearer in iterator adaptors.
Iterators are used heavily in the algorithms in the STL. This is because they can be defined so that all iterators implement the same methods even though they are iterating over many different types.
Description | Pointers | Iterators |
---|---|---|
Define the container | char str[STRSIZE]; | vector<char> str; |
Define the pointer/iterator | char * ptr; | vector<char>::iterator ptr; |
Initialize the pointer/iterator | ptr = str; | ptr = str.begin(); |
Loop over all the characters | while(*ptr != '\0') { | while(ptr != str.end()) { |
Print the character | cout << *ptr << endl; | cout << *ptr << endl; |
Increment the pointer/iterator | ptr++; | ptr++; |
Loop End | } | } |
Loop using For | for(ptr=str; ptr != '\0'; ptr++) | for(ptr=str.begin(); ptr != str.end(); ptr++) |
ptr->method1();The second first dereferences the pointer to get the object and then calls the method.
(*ptr).method1();The first version is slightly faster and easier to read.
Iterators are like pointers, you dereference the pointer
using the * operator.
You can add to the end of a vector using the push_back() method
but there is no push_front() method.
Some examples are:
vector<int> v; vector::iterator i=v.begin(); // i points to the first element in v i++; // move to the next element i--; // move to the previous element v[3]=9; // set element 3 to 9
p++; x=*p;
But iterators are supposed to be incrementable and dereferencable. If cin were an iterator, you would expect to do
cin++to go to the next input element and
*cinto fetch it.
When we have seen problems like this before, we have overloaded the operators to make them act they way we want to. We will do that here by building an adaptor class to make the iostream look like an iterator class. The STL contains an adaptor class called istream_iterator that overloads the increment and dereference operators so that
cin++doesn't do anything. The code probably looks like
istream_iterator & operator++() {};
The * operator simply reads the next thing from the input stream. These iterator adaptors can be used on any input stream including istrstreams and ifstreams.
A common use of iterator adaptors is in some of the STL algorithms that use iterator ranges. For example, the copy() functions moves the objects in the range defined by the first two arguments to the third.
vector<int> v1,v2; // some code to put stuff in v1 copy(v1.begin(),v1.end(),v2.begin());
Starting at the first element in v1, copy it to the first element in v2. Increment both pointers and do it again until we reach the end pointer on v1. One thing you have to be careful about when using the copy() function this way is that it assumes that the destination has the space needed to put the source elements in. A little later we will see a way to make this use push_back() to add elements to the destination.
Now if we could use standard output as an iterator, we could use copy() to send the elements of an array to the output. The standard way to do this is
for(int i=0;i < v.size; i++) cout << v[i];
We can use an ostream_iterator to convert the standard output operator to an iterator.
ostream_iterator<mytype,ptrdiff_t> o(cout)is how you declare an adaptor. The second type to the template can usually be left out. It refers to the memory model needed for compilers that have memory models. The template should default to ptrdiff_t. If it complains, use ptrdiff_t.
Here is an example of using copy to write out a vector.
copy(v.begin(),v.end(), ostream_iterator<int>(cout,"\n"));
The second argument to the adaptor is a separator that is printed after each element.
Input can be done as well.
copy(istream_iterator<int>(cin),istream_iterator<int>(), v.begin());
This still presents the problem of making sure there is enough space in the vector before running this. Another kind of adaptor can be used to fix this.
copy(istream_iterator<int>(cin), istream_iterator<int>(), back_insert_iterator<vector<int>>(v));
The back insert adaptor turns the dereference calls into calls to the push_back() method on vectors.
This example really shows the ability to use classes and operator overloading to do things that would be very difficult in C.
int zero() {return 0;};Then a call to
generate(v.begin(),v.end(),zero);would run each element of v through the zero function and stores the result back in the element. The for_each functions applies the function to each element, but doesn't store the result back in the element. The find_if function applies the function and returns as soon as one of the function calls returns true. It returns an iterator that points to the element that matched the function.