Lecture 8: Streams



Lecture 8: Streams


Input and output. Except for binary I/O, this amounts to mapping objects from and to sequences of characters. I/O is implemented in C++ with a special set of classes. It is made to be type safe and extensible (unlike C's printf() and scanf()) with no compromise in flexibility.
The core I/O facilities are accessed by including a special header file iostream.h. (stream.h can also be included for compatibility with old I/O implementations.) There are four special objects which are pre-constructed (cin, cout, clog and cerr) which handle standard input, output and error streams. I/O is done via the overloaded operators << ("put to" or "insertion" operator for output to cerr, clog and cout) and >> ("get from" or "extraction" for input from cin), or via member functions.
Additional I/O facilities are accessed using iomanip.h (manipulators), fstream.h (files), strstream.h (streams based on C strings) and stdiostream.h (streams based on C FILEs).
8.1 C++ stream library class trees


8.2 class ios
This is the root of all the stream classes. Error state and formatting information are included in class ios.
Error State Flags
The error state consists of four bits: ios::goodbit, ios::eofbit, ios::failbit and ios::badbit. Note in C, printf(), scanf() etc. were stateless. In the C++ stream library, the state allows a multiple "put to" or "get from" statement to behave somewhat like multiple argument calls to printf() or scanf().
The four bits are stored in an int which can be read using:
int rdstate() const;
The state of the badbit could therefore be tested with an expression like:
if (cin.rdstate() & ios::badbit) // handle error ...
else // everything OK ...
A more convenient way of testing the individual bits is provided by the following member functions:
int good() const;
int eof() const;
int fail() const;        // non-fatal error - failed to read expected data
int bad() const;         // fatal error - stream can no longer be used
operator !() const;      // returns non-zero if good not set
operator void *() const; // returns non-NULL if good set
The error state can be set using:
void clear(int = 0);
The default value of zero results in ios::goodbit being set.
clear();
is therefore equivalent to
clear(0);
which is equivalent to
clear(ios::goodbit);
Note that ios::goodbit is a non-zero value. clear() might be used to set one of the other bits as part of a programmer's code for operator>>() for a particular object. For example:
if (bad_char) is.clear(ios::badbit); // set istream's badbit
Formatting Information
A fair amount of the formatting information is in the form of flags which are contained in a long int. These flags retain their settings until explicitly changed. The crudest access is:
long flags() const; // reads the flags
long flags(long);   // sets flags and returns previous setting
This sort of access should be used only to read the flags as a whole so they can be restored later. A function might do this if it needed specific formatting, but also needed to leave the state of the formatting flags of a stream argument unchanged.
Example:
void f(ostream &os)
{
  long flags = os.flags(); // record original state of formatting flags
  ...
  os.setf(ios::basefield, ios::oct);
  ...
  os.flags(flags); // restore original state of formatting flags
}
Control of individual flag settings can be achieved via:
long setf(long);   // set one or more individual flags
                   // (not a member of a group)
                   // or use setiosflags(long) manipulator
long unsetf(long); // reset one or more individual flags
                   // (not a member of a group)
                   // or use resetiosflags(long) manipulator
Both the setf() functions and unsetf() return the previous value of the flags. This applies to:
ios::boolalpha // insert/extract boolean type in alphabetic format
ios::skipws    // this is the only one that defaults to true
ios::showbase  // add 0 or 0x to non-decimal printouts
ios::showpos   // adds + sign to positive values
ios::uppercase // use X, A-F for hex and E for exponential
ios::showpoint // trailing zeros and decimal point always appear in floats
               // formatted as if all trailing digits were non-zero
ios::unitbuf   // flush ostream after each output operation
A two argument version of setf() is used to set flag which is a member of a group of flags. The second argument specifies the group and is a bitwise OR of all the flags in the group. The specified bit is set and the rest are unset. This function is:
long setf(long, long group);
The groups are:
ios::adjustfield  // padding position
  ios::left       // left aligned
  ios::right      // right aligned (this is the default)
  ios::internal   // between sign or base and value
 
ios::basefield    // or use setbase(int = 0, 10, 8 or 16) manipulator
  ios::dec        // or use dec manipulator (default)
  ios::oct        // or use oct manipulator
  ios::hex        // or use hex manipulator
 
ios::floatfield   // neither flag is set by default!
  ios::scientific
  ios::fixed
The last group is a little odd in that it makes sense for both flags to be unset. This is a shadow "automatic" state and is comparable to the
%g
format of printf().
Other formatting information is set by the following functions. For width(), this information is temporary, and the default width (0) is returned to after a field is inserted.
The effect of the ios::precision(int) method differs depending on which of the three possible ios::floatfield states governs floating-point formatting. In the default "automatic" state when neither bit is set, it represents the total number of digits used. When ios::fixed is set, it is the number of digits after the decimal point. When ios::scientific is set, it is the number of digits in the mantissa.
char fill(char);       // set fill char; or use setfill(char) manipulator
char fill() const;     // find current value of fill char (default is ' ')
int precision(int);    // number of floating point digits displayed
int precision() const; // or use setprecision(int) manip (default is 6)
int width(int);        // or use setw(int) manip
int width() const;     // default is 0 (use just the amount of space needed)
It also provides an extra set of format flags and long ints where user information can be stored by derived classes.
Two miscellaneous capabilities:
A method for tying an istream to an ostream is available so that the ostream gets flushed before any input operation. cout and cin are tied by default. The ostream *ios::tie(ostream *) method takes and returns a pointer to an ostream. The pointer returned is the previous tie. Tying to 0 breaks any existing tie. It can only be tied to one ostream at a time.
ios::sync_with_stdio() resets cin, cout, cerr, clog to use stdiobufs and thus they are synchronized with the corresponding FILEs stdin, stdout and stderr. I/O can only be mixed on a line-by-line basis.
8.3 Input
Pre-defined object cin (class istream with public base ios).
Result of >> operator is istream &. In combination with left-to-right associativity of >>, this means the right thing happens. For example:
cin >> x >> y;
Notice that, though non-const objects must be used, pointers are not used as in C since >> is overloaded using reference arguments. There is also an ios converter (to void *) which allows istream objects to appear as control expressions. It converts to 0 pointer if fail or bad bit is set. fail flag must be cleared to continue input (bad flag set in addition indicates more fundamental problem). Use clear() member function to do this:
int x; char c;
 
while (cin) // get stream of integer values
{
  while (cin >> x) { cout << x; process(x); }
  cin.clear();
  while (cin.get(c) && c != '\n'); // flush line from stream on error
}
Without the cin.clear() call, cin.get(c) would just be a no-op. This is different from the behavior of C's scanf() routine where succeeding calls are not affected by failures in previous calls. As in C, a failed operation must be registered before flag is set, so it should be tested after an input operation, not before.
Member functions:
istream(streambuf *);
istream &ignore(streamsize, int = EOF);
int peek();
istream &putback(char &);
int get(); // like C getchar();
istream &unget(); // putback most recent char read
istream &get(char &);
istream &get  // always terminates buffer with '\0'
              // doesn't extract terminator char from stream
(char *, streamsize, char = '\n');
istream &getline // same except extracts
                 // terminator char from stream
(char *, streamsize, char = '\n');
istream &read(char *, streamsize); // binary input
streamsize readsome(char *, streamsize); // binary input
istream &seekg(streampos);  // set position indicator
istream &seekg(streamoff, seek_dir); // dir is beg, cur or end
streampos tellg() const; // p suffixes stand for "put"
int gcount() const; // number of chars extracted by last unformatted
                    // input function (get, getline, ignore, read)
8.4 Note on random access
Notice that class ios doesn't support random access as one might suspect given the behavior of the C stdio routines seek() and tell() which work on all files. This is because in read/write situations separate positions are maintained for doing input and output. So these methods are supported at the istream and ostream level.
8.5 Output
Pre-defined objects cerr, clog and cout (class ostream with public base ios). Note cerr is not line buffered as in C while cout retains line buffering. clog is line buffered and is an alternative interface to the error stream.
In printing expressions, be careful to use parens if expressions' operators have greater or equal precedence compared with <<. Result of << operator is ostream &. In combination with left-to-right associativity of << this means the right thing happens with (for example):
cerr << "x = " << x;
The type of character constants is char in C++ version 2.0 and after, not int as in C and C++ version 1.0. Putting a char to an ostream results in the character corresponding to the code being printed. To get the integer code printed, the character must be cast to an int.
User-defined types are output by overloading the << operator. Since an ostream & is the first argument for this binary operator, it can't be implemented as a member function of the object being output, but is done using a free-standing function whose second argument is the object being output.
Implementing input for user-defined types is like output except it may be appropriate to change the state of the input stream if an operations fails. For example, complex input routine to get a complex value in the form (2.2, 3.3) would fail and set the bad bit if the sequence of characters retrieved from the istream don't fit the prescribed format. Perhaps setting the fail bit would be appropriate if it had saved the characters and put them back in the event of a failure. The ios::clear() function is used to set the state of a stream.
Member functions:
ostream &put(char);
ostream &flush();
ostream &write(const char *, streamsize);   // binary output
ostream &seekp(streampos),
ostream &seekp(streamoff, seek_dir); // seek_dir is ios::beg,
                                         // ios::cur or ios::end
streampos tellp() const;  // p suffixes stand for "put"
operator <<(streambuf *); // transfers characters to its own streambuf
                                // from this one until it can't find any more
8.6 Files (devices)
The header fstream.h contains definitions for fstream (derived from iostream which is in turn derived from istream and ostream), ofstream (derived from ostream) and ifstream. These are constructed with a character string containing the name of the file and an optional mode composed of bitwise-ORd flags derived from an enum defined in ios. Flags are:


0 comments: