Code Capsules

C++ Streams

Chuck Allison


Chuck Allison is a regular columnist with CUJ and a software architect for the Family History Department of the Church of Jesus Christ of Latter Day Saints Church Headquarters in Salt Lake City. He has a B.S. and M.S. in mathematics, has been programming since 1975, and has been teaching and developing in C since 1984. His current interest is object-oriented technology and education. He is a member of X3J16, the ANSI C ++ Standards Committee. Chuck can be reached on the Internet atallison@decus.org, or at (801)240-4510.

Like C, C++ does not provide language facilities for input or output. They are, instead, part of the standard library. Although you can use C's stdio functions for input and output in C++, the stream classes are an efficient way to read and write data in C++. The stream classes of C++ offer three advantages to C's stdio functions.

First, streams are type-safe. The printf/scanf family of functions depends solely on a format string that the compiler cannot validate. Since the type of an object in C++ determines which action follows, you just can't do the wrong thing without hearing about it at compile time (unless you try really hard).

Second, streams provide a uniform interface for all input and output operations. Whether the source or destination is a file, a string, or a standard device, the operations are identical ("A stream is a stream is a stream...").

Finally, streams are extensible — and I don't just mean that you can derive new stream classes. Almost always, just overloading standard stream operators is all you need.

Standard Streams

There are four pre-defined streams: cin (standard input), cout (standard output), cerr (standard error), and clog (standard error). All but cerr are fully-buffered streams. Like stderr, cerr behaves as if it is unbuffered, but it is actually unit-buffered, meaning that it flushes its buffer automatically after processing each object, not after each byte. For example, with unit-buffering, the statement

cerr << "hello";
buffers the five characters and then flushes the buffer. An un- buffered approach sends a character at a time all the way through to its destination.

Simple I/0

The following program copies standard input to standard output:

// copy1.cpp: Copy standard
input to standard output
#include <iostream.h>

main()
{
    char c;

    while (cin.get (c))
        cout.put(c);

    return 0;
}
get, called an extractor, reads a single character or a string from a stream, depending on how it's called. (In this case, get reads a single character from the cin stream.) put, called an inserter, sends the character, or string, to an object. (In this case, put sends a character to the cout object.)

The get extractor stores the next byte from a stream into its char parameter. Like most stream member functions, get returns the stream itself. When a stream appears in a boolean context (like in the while loop above) it tests true if the data transferred successfully, and false if there was an error, such as an attempt to read beyond end-of-file. Although such simple boolean tests suffice most of the time, you can query the state of a stream anytime with the following member functions:

The program in
Listing 1 copies a text file line-by-line. The getline extractor reads up to BUFSIZ-1 characters into s, stopping if it finds a newline character, and appends a null byte. (It discards the newline.) Output streams use the left shift operator as an inserter. Any object, built-in or user-defined, can be part of a chain of insertions into a stream. (However, you must overload << yourself for your own classes.)

Listing 2 contains a program that illustrates the extraction operator >>. Since in C you normally use stderr for prompts (because it is unbuffered), you might be tempted to use cerr in C++:

cerr << "Please enter an integer: ";
cin >> i;
However, this is not necessary in C++ because cout is tied to cin. An output stream tied to an input stream is automatically flushed when input is requested. If necessary, you can use the flush member function to force a flush.

Addresses print in an implementation-defined format (usually hexadecimal). Character arrays are, of course, an exception — the string value is printed, not the address. To print the address of a string, cast it to a void *:

cout << (void *) s << '\n'; // prints address
The >> extraction operator skips whitespace by default. The program in Listing 3 uses this feature to count the words in a text file. Extracting into a character string behaves like the %s edit descriptor in scanf. It is possible to turn off the skipping of whitespace when reading characters (see Listing 4) .

Beyond Simple I/O

Formatting

ios::skipws in Listing 4 is an example of a format flag. Format flags are single-bit values that you can set with the member function setf, and reset with unset. (See Table 1 for a description.)

The program in Listing 5 illustrates numeric formatting. The member function precision dictates the number of decimal places to display for floating-point values. Unless the ios::show-point flag is set, however, trailing zeroes will not appear. ios::showpos prints positive numbers with a leading plus sign. ios::uppercase displays the x in hexadecimal values and the e in exponential in upper case.

Some formatting options can take on a range of values. For example, ios::basefield, which determines the numeric base for displaying integers, can be set to decimal, octal, or hexadecimal. (See Table 2 for a description of the three format fields available.) Since these are bit fields and not single bits, you set them with a two-parameter version of setf. For example, the program in Listing 6 changes to octal mode with the statement

cout.setf(ios::oct,ios::basefield);
With the flag ios::showbase set, octals print with a leading 0 and hexadecimals with a leading 0x (or 0X if ios::uppercase is also set).

Manipulators

endl, of a manipulator, inserts a newline character into the output stream and then flushes the stream. Instead of inserting an object into the stream, a manipulator changes a stream state. Table 3 contains built-in manipulators declared in iostream.h. The program in Listing 7, although functionally equivalent to the one in Listing 6, uses manipulators instead of explicit calls to setf. Manipulators often allow for more streamlined code.

Other manipulators take parameters (see Table 4) . The program in Listing 8 uses the setw(n) manipulator to set the output width directly in the insertion sequence, so you don't need a separate call to width. The special field ios::width is reset to 0 immediately after every insertion. When ios::width is 0, values print with the minimum the number of characters necessary. As usual, numbers are not truncated, even if you don't allow enough space for them.

You could replace the statement

cout.fill('#');
with the in-sequence manipulator

... << setfill('#') << ...
but it seems cumbersome to do so in this case.

Extractors usually ignore width settings. An exception is string input. You should set the width field to the size of a character array before extracting into it in order to avoid overflow. When processing the input line

nowisthetimeforall
the program in Listing 9 produces

nowisthet,im,eforall
Remember that extractors use whitespace as a delimiter, so if the input is

now is the time for all
then the output is
now, is, the
You can create your own manipulators by simply defining how a function takes a stream reference parameter and returns that same reference. For example, here is a manipulator that rings the bell at the console when you insert it into any output stream:

// A manipulator that beeps
#include <iostream.h>

ostream& beep(ostream& os)
{
    os << (char) 7; // ASCII BEL
    return os;
}
To use this, just insert it:

cout << ... << beep << ...

Beyond the Standard Streams

String Streams

To read from and write to strings in memory (like sscanf and sprintf do), use the string stream classes declared in strstream.h. The program in Listing 10 uses an anonymous output string stream to write to the string s. The line

ostrstream(s,sizeof s) << "You entered " << i << ends;
is a safe alternative to the C statement

sprintf(s,"You entered %d",i);
If you want the stream to persist beyond the statement, declare an object:

ostrstream os(s,sizeof s);
os << "You entered " << i << ends;
Listing 11 contains a program that counts both the number of lines and the number of words in a text file. It reads a line at a time and then attaches a string stream to the line to extract each word. NOTE: the current release (v3.1) of the Borland C++ compiler requires a slight change in the while loop:

while (line >> word && word[0])
    ++wc;
(For some reason the string stream extractor doesn't set the state properly).

The getline function normally extracts characters up to a newline. You can use another character as a delimiter by sup- plying the optional third argument. For example, the statement

sstr.getline(s,sizeof s,',');
reads characters into s up to the next occurrence of a comma, then discards the comma. This is useful for reading database files with delimited fields. The program in Listing 12 reads a comma-delimited file with fields representing name, address, city, state, and zip code. For example, with the following input:

Bill CIinton, 1600 Penn. Ave.,Washington,DC,21234
Ross Perot,1234 Feedback Dr.,Big Ears,TX,5678g
Annymous,, Nowhere,, 00000
it produces this output:

Bill Clinton|1600 Penn. Ave.|Washington|DC|21234
Ross Perot|1234 Feedback Dr.|Big Ears|TX|5678g
Anonymous||Nowhere||00000

File Streams

The program in Listing 13 copies one file to another by explicitly opening file streams. You must enter both file names on the command line. The definition

ifstream inf (argv [1] )

attempts to open the file named in the first command-line argument and connects the input stream inf to it. If all went well, inf will test true as a boolean expression (analogously for outf).Both files close automatically when the file streams are destroyed as they go out of scope.

Listing 14 contains a more flexible copy utility. It allows you to either name the files on the command line:

copy fi1el fi1e2
or to use redirection:

copy <filel >file2
or to leave one or both files connected to a standard device:

copy file1 {displays file1 on the console)
If you supply a file name it opens the file, otherwise it uses standard input and/or standard output. In order to attach a file stream to an open file you need a file handle, such as ileno returns. (See last month's "Code Capsule" for information on fileno and other POSIX functions). When a file stream goes out of scope, its destructor closes the associated file, unless attach made the connection (otherwise standard I/O would shut down!).

The program in Listing 15 creates a file of fixed-length records, and then reads back and displays selected records. An fstream object allows simultaneous input and output on the same file. The flag ios::bin enables binary mode, which is necessary for accurate file positioning on non-UNIX systems. (Some compilers use ios::binary or ios: :notranslate, but the current working paper for standard C++ specifies ios::bin). The member functions read and write perform binary input and output, similar to their C counterparts read and write (except that the former process only one record at a time). seekg moves the file-read pointer to a specified byte position in the file. Its second argument controls positioning in the usual way:

Note: Classes that specifically support simultaneous input and output on a stream (namely, strearn, strstream, and iostream) are not part of the proposed standard for C++. The committee decided to simplify the standard library by eliminating the use of multiple inheritance. (Multiple inheritance is one of those necessary evils that you should only use when absolutely.) To open a file for both input and output in a future conforming implementation, you will have to replace

int mode = ios::bin | ios::trunc | ios::out | ios::in;
Fstream f("recs.dat',mode);
with

int mode = ios::bin | ios::trunc | ios::out | ios::in;
ofstream f("recs.dat", mode);
ifstream g(f.rdbuf(),ios: :bin);
which causes streams and g to share the same internal buffer. (See Table 5 for descriptions of the file open modes.)

Streams and User-Defined Objects

To read or write a user-defined object usually reduces to processing built-in types which comprise the object (or its components, etc.). For example, suppose you wanted to define an inserter for a complex number class:

class complex
{
    double real, imag;
public:
    friend ostream& operator<<(ostream&, const complex&);
    // Details omitted
};
To display a complex number in coordinate form (e.g., (2,7.5)), you simply use the built-in inserter on the components:

ostream& operator<<(ostream& os, const complex& c)
{
    os << '{' << c.real << ',' << c.imag << ')';
    return os;
}
The semantics of operator<< require returning the stream to allow chaining multiple insertions in a single statement. (For more detail on overloading inserters and extractors, see Dan Saks' column "Stepping Up to C++" in the May 1992 and July 1992 issues of CUJ.)

There is more machinery to streams than what I have presented here. (If you're interested in what's under the hood, look up the streambu family of classes in your compiler's documention). I have illustrated the high-level features that should suffice for most non-systems applications. If things appear slightly more involved that what you're used to in C, that merely reflects the careful design that makes streams safer and more robust than the stdio functions.