10.4.2. iostream Iterators

Image

Even though the iostream types are not containers, there are iterators that can be used with objects of the IO types (§ 8.1, p. 310). An istream_iterator (Table 10.3 (overleaf)) reads an input stream, and an ostream_iterator (Table 10.4 (p. 405)) writes an output stream. These iterators treat their corresponding stream as a sequence of elements of a specified type. Using a stream iterator, we can use the generic algorithms to read data from or write data to stream objects.

Table 10.3. istream_iterator Operations

Image

Table 10.4. ostream Iterator Operations

Image
Operations on istream_iterators

When we create a stream iterator, we must specify the type of objects that the iterator will read or write. An istream_iterator uses >> to read a stream. Therefore, the type that an istream_iterator reads must have an input operator defined. When we create an istream_iterator, we can bind it to a stream. Alternatively, we can default initialize the iterator, which creates an iterator that we can use as the off-the-end value.

istream_iterator<int> int_it(cin);   //   reads ints from cin
istream_iterator<int> int_eof;       //   end iterator value
ifstream in("afile");
istream_iterator<string> str_it(in); //   reads strings from "afile"

As an example, we can use an istream_iterator to read the standard input into a vector:

istream_iterator<int> in_iter(cin);  // read ints from cin
istream_iterator<int> eof;           // istream ''end'' iterator
while (in_iter != eof)  // while there's valid input to read
    // postfix increment reads the stream and returns the old value of the iterator
    // we dereference that iterator to get the previous value read from the stream
    vec.push_back(*in_iter++);

This loop reads ints from cin, storing what was read in vec. On each iteration, the loop checks whether in_iter is the same as eof. That iterator was defined as the empty istream_iterator, which is used as the end iterator. An iterator bound to a stream is equal to the end iterator once its associated stream hits end-of-file or encounters an IO error.

The hardest part of this program is the argument to push_back, which uses the dereference and postfix increment operators. This expression works just like others we’ve written that combined dereference with postfix increment (§ 4.5, p. 148). The postfix increment advances the stream by reading the next value but returns the old value of the iterator. That old value contains the previous value read from the stream. We dereference that iterator to obtain that value.

What is more useful is that we can rewrite this program as

istream_iterator<int> in_iter(cin), eof;  // read ints from cin
vector<int> vec(in_iter, eof);  // construct vec from an iterator range

Here we construct vec from a pair of iterators that denote a range of elements. Those iterators are istream_iterators, which means that the range is obtained by reading the associated stream. This constructor reads cin until it hits end-of-file or encounters an input that is not an int. The elements that are read are used to construct vec.

Using Stream Iterators with the Algorithms

Because algorithms operate in terms of iterator operations, and the stream iterators support at least some iterator operations, we can use stream iterators with at least some of the algorithms. We’ll see in § 10.5.1 (p. 410) how to tell which algorithms can be used with the stream iterators. As one example, we can call accumulate with a pair of istream_iterators:

istream_iterator<int> in(cin), eof;
cout << accumulate(in, eof, 0) << endl;

This call will generate the sum of values read from the standard input. If the input to this program is

23 109 45 89 6 34 12 90 34 23 56 23 8 89 23

then the output will be 664.

istream_iterators Are Permitted to Use Lazy Evaluation

When we bind an istream_iterator to a stream, we are not guaranteed that it will read the stream immediately. The implementation is permitted to delay reading the stream until we use the iterator. We are guaranteed that before we dereference the iterator for the first time, the stream will have been read. For most programs, whether the read is immediate or delayed makes no difference. However, if we create an istream_iterator that we destroy without using or if we are synchronizing reads to the same stream from two different objects, then we might care a great deal when the read happens.

Operations on ostream_iterators

An ostream_iterator can be defined for any type that has an output operator (the << operator). When we create an ostream_iterator, we may (optionally) provide a second argument that specifies a character string to print following each element. That string must be a C-style character string (i.e., a string literal or a pointer to a null-terminated array). We must bind an ostream_iterator to a specific stream. There is no empty or off-the-end ostream_iterator.

Table 10.4: ostream_iterator Operations

Image

We can use an ostream_iterator to write a sequence of values:

ostream_iterator<int> out_iter(cout, " ");
for (auto e : vec)
    *out_iter++ = e;  // the assignment writes this element to cout
cout << endl;

This program writes each element from vec onto cout following each element with a space. Each time we assign a value to out_iter, the write is committed.

It is worth noting that we can omit the dereference and the increment when we assign to out_iter. That is, we can write this loop equivalently as

for (auto e : vec)
    out_iter = e;  // the assignment writes this element to cout
cout << endl;

The * and ++ operators do nothing on an ostream_iterator, so omitting them has no effect on our program. However, we prefer to write the loop as first presented. That loop uses the iterator consistently with how we use other iterator types. We can easily change this loop to execute on another iterator type. Moreover, the behavior of this loop will be clearer to readers of our code.

Rather than writing the loop ourselves, we can more easily print the elements in vec by calling copy:

copy(vec.begin(), vec.end(), out_iter);
cout << endl;

Using Stream Iterators with Class Types

We can create an istream_iterator for any type that has an input operator (>>). Similarly, we can define an ostream_iterator so long as the type has an output operator (<<). Because Sales_item has both input and output operators, we can use IO iterators to rewrite the bookstore program from § 1.6 (p. 24):

istream_iterator<Sales_item> item_iter(cin), eof;
ostream_iterator<Sales_item> out_iter(cout, " ");
// store the first transaction in sum and read the next record
Sales_item sum = *item_iter++;
while (item_iter != eof) {
    // if the current transaction (which is stored in item_iter) has the same ISBN
    if (item_iter->isbn() == sum.isbn())
        sum += *item_iter++; // add it to sum and read the next transaction
    else {
        out_iter = sum;      // write the current sum
        sum = *item_iter++;  // read the next transaction
    }
}
out_iter = sum;  // remember to print the last set of records

This program uses item_iter to read Sales_item transactions from cin. It uses out_iter to write the resulting sums to cout, following each output with a newline. Having defined our iterators, we use item_iter to initialize sum with the value of the first transaction:

// store the first transaction in sum and read the next record
Sales_item sum = *item_iter++;

Here, we dereference the result of the postfix increment on item_iter. This expression reads the next transaction, and initializes sum from the value previously stored in item_iter.

The while loop executes until we hit end-of-file on cin. Inside the while, we check whether sum and the record we just read refer to the same book. If so, we add the most recently read Sales_item into sum. If the ISBNs differ, we assign sum to out_iter, which prints the current value of sum followed by a newline. Having printed the sum for the previous book, we assign sum a copy of the most recently read transaction and increment the iterator, which reads the next transaction. The loop continues until an error or end-of-file is encountered. Before exiting, we remember to print the values associated with the last book in the input.


Exercises Section 10.4.2

Exercise 10.29: Write a program using stream iterators to read a text file into a vector of strings.

Exercise 10.30: Use stream iterators, sort, and copy to read a sequence of integers from the standard input, sort them, and then write them back to the standard output.

Exercise 10.31: Update the program from the previous exercise so that it prints only the unique elements. Your program should use unqiue_copy10.4.1, p. 403).

Exercise 10.32: Rewrite the bookstore problem from § 1.6 (p. 24) using a vector to hold the transactions and various algorithms to do the processing. Use sort with your compareIsbn function from § 10.3.1 (p. 387) to arrange the transactions in order, and then use find and accumulate to do the sum.

Exercise 10.33: Write a program that takes the names of an input file and two output files. The input file should hold integers. Using an istream_iterator read the input file. Using ostream_iterators, write the odd numbers into the first output file. Each value should be followed by a space. Write the even numbers into the second file. Each of these values should be placed on a separate line.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.174.23