The iterator pattern applies to any sort of sequence of elements, whatever that sequence's implementation may be. An interesting take on this is found in the stream iterators in the standard library.
A stream iterator is an iterator interface to a stream. Here's a simple example of reading a few strings from
list<string> strs; cout << "Enter some strings, (enter, ctrl-z, enter to quit): "; istream_iterator<string> input(cin); while (!cin.eof()) strs.push_back(*input++);
This creates an iterator named
input that iterates over elements read from the stream. The elements read from the stream are those that would be read by using the
>> operator, as in
cin >> str. It reads until
cin.eof returns true, which, on my Windows XP machine, is when I press
You can also use the one-past-the-end semantics on input streams. If you invoke the stream constructor with no arguments, the
istream_iterator object it creates is an end marker. For example, instead of using
cin.eof above, I could have done this instead:
istream_iterator<string> input(cin); istream_iterator<string> inputEnd; while (input != inputEnd) strs.push_back(*input++);
In fact, this is what makes stream iterators work so well with standard algorithms. Consider the opposite situation of writing the contents of a container to an output stream. Here's a way to do that:
list<string> lst; lst.push_back("banana"); lst.push_back("apple"); lst.push_back("strawberry"); ostream_iterator<string> outStr(cout, " "); copy(lst.begin(), lst.end(), outStr);
This will dump the contents of
cout, and delimit each one with a space. The constructor of
ostream_iterator accepts an optional delimiter as its second argument.
Finally, stream iterators work with any kind of stream, so you can use them for file streams, too. Here's an example of reading elements from a file into a
ifstream in("data.txt"); istream_iterator<string> start(in); istream_iterator<string> end; vector<string> v(start, end);
These few lines do quite a bit. The first line opens a file stream from my text file named data.txt. Then I create two input stream iterators,
end, which I described a moment ago. The last line emphasizes how iterators make the details of their sources irrelevant. I create a string,
vector v, using the range constructor (which copies the elements in the range into the new
vector), and give it the two stream iterators I just created. This works the same as the first example in this section: it reads elements from the input stream until the stream reaches an end marker. This is a quick way to read elements from a stream into a container.
I find stream iterators more useful for debugging than anything else, but they illustrate an important point: that any sort of sequence of elements is a candidate for an iterator interface. The next section explains how you can add an iterator interface to your own classes.