EECS 211
How to read Chapter 15 and 17


One of the most useful abstractions that most modern programming languages have implemented is the I/O stream. A program reads data, e.g., characters, from an input stream, and writes them to an output stream. The stream abstraction separates these concepts of source and destination from concepts such as files, console, or string. In the pre-stream days, you'd have one set of functions for reading from and writing to the console, and another, distinct but very similar, set of functions for reading from and writing to a file, and, often, no functions for reading data from or writing data to a string. In C for example you had printf to write to the console, fprintf to write to a file, and sprintf to write to a string.

This does more than reduce the number of different I/O functions. It also means that when you overload the insertion and extraction operators for a class, you're immediately getting an easy way to store and retrieve instances of your class from strings and files. For example, suppose you overload the insertion operator operator<< for Rational, e.g.,

ostream &operator<<( ostream &out, const Rational &r )
  return out << r.numerator() << "/" << r.denominator();

Now you can write a rational to the console:

cout << Rational( 3, 6 )  << endl;

You can also write it to a file:

ofstream out("example.txt");
out << Rational( 3, 6 )  << endl;

You can also write it to a string:

ostringstream out;
out << Rational( 3, 6 )  << endl;

The same thing holds true for input streams.

Streams also make testing a lot easier. If you have a program that processes data in files, writing tests can be tedious because you have to make sure that the files exist for reading. If your program writes files, you have to remember to delete any old files before running the test, run the test, read the files and check the output, and finally delete the files.

On the other hand, with string input and output streams, things are simple, if your program includes functions that take streams. Your test code will have the sample input data in strings. It will create a string input streams for the data and pass the streams to your program. If your program writes output, the test code will create output string streams and pass them. After the program runs, all the test code has to do is check what's in the output string. Much much simpler and much much faster than working with files.


Files are important because they can hold large amounts of data, and they are persistent. Persistent means that they don't disappear when the program stops.

There are a few downsides to files, however: