C++ I/O Streams


Meaning of Streams

Like C, C++ uses a stream model for input. Conceptually a stream is an unbroken line of string tokens separated by delimiters, such as whitespace and newline characters. So while you may view your input as neatly divided into lines as follows:

jan 10 30 15
feb 10 25 12
C++ views it as a single stream:
jan 10 30 15 \n feb 10 25 12 \n
The C++ input operators "pop" the string tokens off the stream, convert them to the appropriate type, and then store them in the variables specified by the user. The C++ output operators convert values to strings and then "push" them onto a stream. The output device, such as a screen, printer, or file, then interprets the formatting characters embedded in the stream, such as \t and \n, to produce the formatted output that you see.

We will concern ourselves with the following types of character streams:

  1. iostream: streams associated with stdio
  2. fstream: streams associated with files
  3. sstream: streams associated with strings

In order to use these streams in your program you need to place the following include statements at the top of your program:

#include<iostream> #include<fstream> #include<sstream>


Standard I/O

cin, cout, and cerr are pre-defined variables that are associated with stdin, stdout, and stderr. The >> operator allows you to read data (e.g., cin >> a) and the << operator allows you to write data (e.g., cout << b). You can cascade both >> and <<: cin >> a >> b; or more neatly: cin >> a >> b;


C++ I/O Advantages Over C

C++ stream objects provide two advantages over their C counterparts:

  1. The C++ compiler determines the type of variable being read or written and the stream I/O operators automatically convert an input value to the appropriate type and automatically convert an output value to an appropriate printed string

  2. You can test input stream objects to determine if an input value had the appropriate type (e.g., if an integer was really read into an integer type)
Unfortunately, C++ does not fix C's inability to deal with formatted input:
  1. It is not easy to read data files with delimiters, such as commas. For example, there is no easy way to have C++ read the following line and automatically break it up into an array of strings:
         Brad, Vander Zanden, 1678 Cardiff Rd., Columbus, Ohio, 43221
         

  2. It is not easy to read formatted input files. Formatted input files are files with fixed width fields. Both C and C++ provide I/O operators that allow you to read a fixed number of characters but they get read as a string, not as an appropriate type. It would be nice if C would let you write:
         scanf("%10s%5d%7.2f", &name, &zip_code, &salary);
         
    but you can't and C++ doesn't allow you to either.


Opening and Closing ifstreams and ofstreams

You can open a file for input or output by passing its filename to the constructor for the appropriate stream object. For example:

  ifstream transaction_file(argv[1]);
  
Call the close to close a file
  transaction_file.close();
  
If you are opening a file for output and it already exists, the ostream's default behavior is to overwrite an existing file. If you want to append to the file instead, you can pass the ios::app flag to the constructor as a second argument:
  ofstream output_file("myfile", ios::app);
  

To check whether or not a file was properly opened, you can "test" the input stream:

     ifstream input_file("myfile");
     if (!input_file) {
       ... error processing ...
     }
     


Result flags

When a stream performs an I/O operation it sets various result flags to indicate the outcome of the operation. The following methods query these flags:

You clear these flags and reset the stream condition to good by calling the clear method. You must call clear after an I/O operation fails in order to allow I/O operations to continue.

A stream will evaluate to a boolean value if placed in a conditional statement. The boolean value will be true if the current result flag is good; otherwise it will be false. That is why you can "test" the stream variable after you try to open the file. If the file failed to open then the stream's fail flag will be set to false and the stream's variable will evaluate to false.

Similarly the stream's eof flag will be set to false when it fails to read input because it reaches the end of the file. Hence you can write:

     while(cin >> value) {
       cout << value;
     }
     
and be assured that the loop will exit on eof. However, it would also exit if invalid input is received since in that case the fail flag would be set to false. Hence if you need to perform error checking you should write something like: int value; cin >> value; while (!cin.eof()) { if (cin.good()) { ... process the value ... } else { // input was not an integer cin.clear(); // reset the stream so that we can continue reading string bad_value; cin >> bad_value; // read the bad_value and print it out cerr << "bad value: " << bad_value << endl; } cin >> value; }

The above code makes use of a technique called a priming read. Before the code enters the loop, it tries to read an initial value. This first read is called a priming read because it "primes" the input variable, in this case value and it also primes the result flags in cin. The priming read allows the loop to check for eof before entering the loop and makes the loop logic easier to read. The second part of the priming read technique occurs at the bottom of the loop, where I read the next value, thus "priming" the input variable and the result flags for the next iteration of the loop.


getline and string streams

One problem with the >> operator is that it ignores newlines when reading data. Hence if you think that a line of input should have four items but it only has three, and you attempt to read that line of input with the >> operator, then the >> operator will grab the first item from the following line. For example, if your statement is:

cin >> month >> low >> high >> close; and your input is:
jan 10 30
feb 10 25 12
then cin will attempt to read "feb" into close, because the first line has only three items. If you want to read a single line at a time and ensure that your input operator does not read beyond it, then you should call the getline function. There are two forms of getline, the method form that can be called on a stream object and the global form that must take a stream object as an argument.

The syntax for the method form is:

streamObj.getline(char *, int num_bytes); streamObj.getline(char *, int num_bytes, char delimiter); The first form allows you to read num_bytes into a C-style string and the second form allows you to terminate the reading of the line if the delimiter character is reached (e.g., ','). getline also will terminate if it encounters the newline character. The newline character or the delimiter get discarded.

One obvious drawback of the method form is that it does not allow you to read into C++ strings and another drawback is that you cannot simply ask getline to read the entire line. You can accomplish both these tasks by using the global version of the getline function:

getline(istream | ifstream, string);
getline(istream | ifstream, string, char delimiter);
The first form reads a line into your string variable and the second form reads a line up to the delimiter into your string variable. Both the newline character and the delimiter get discarded.

You can now extract the individual string tokens from the line by passing the string to a istringstream object via the str method:

string transaction; ifstream transaction_file("transactions"); istringstream string_tokenizer; getline(transaction_file, transaction); string_tokenizer.str(transaction); string_tokenizer >> month >> low >> hi >> close; if (string_tokenizer.eof()) { cout << "error--too few fields: " << transaction; } else cout << month << " " << low << " " << hi << " " << close << endl

Formatting Output

In C you format output using flags like %s and %7.2f. In C++ you call methods associated with the output stream classes. For example, to left justify a string in a 20 character field and terminate it with a newline, you would write:

cout << left << setw(20) << myString << endl;
left, setw, and endl are called io manipulators, but they can also be thought of as formatting methods. You can consult a C++ text to find out about all the formatting methods provided by output streams. These notes will cover the most common ones.

First, you will find that the ios class defines many constants to control output formatting. The ios class is the base class for streams and the constants are referenced by prefixing them with a ios:: prefix.

Next you can control the format of your output in one of two ways. You can call either call formatting methods provided by the stream or you can pass iomanipulators to the << operator.

The important formatting methods are:

  1. setf/unsetf(format flags): setf sets the flag and unsetf disables the flag. The most useful format flags are:

  2. width(int): Sets the width of the output field. If the value being output is smaller than the width, then the remaining part of the field with be padded with blanks, or if a fill character is specified, then a fill character

  3. precision(int): The number of digits to print. If the ios::fixed flag is set, then precision specifies the number of digits to print after the decimal point. If the ios::flag is not set (and it potentially resets after a variable is printed), then precision specifies the total number of digits before and after the decimal point. Examples: cout.precision(3); cout << 123456.12548 << endl; output 1.23e+05 cout.setf(ios::fixed); cout.precision(3); cout << 123456.12568 << endl; output 123456.126 (note the rounding) cout.precision(3); cout << .1257 << endl; output: .126 (note the rounding)
  4. fill('*'): Fill character for padding cout.fill('*'); cout.width(10); cout << 123 << endl; output: *******123

io manipulators

io manipulators provide an inline way to specify the formatting and flag options to the << operator. For example, the line:

cout << left << setw(10) << "brad" << endl; left aligns "brad" in a field of width 10 and then prints a newline character. This statement makes use of three io manipulators: left, setw, and endl.

In order to use io manipulators in your program you must place the following include statement at the top of your program:

#include<iomanip> Here is a list of the most useful io manipulators:

  1. boolalpha
  2. endl
  3. fixed
  4. showpoint
  5. left (left justification)
  6. right (right justification)
  7. setw(int)
  8. setprecision(int)
  9. setfill(char)
Here are some more examples: cout << setw(10) << setfill('*') << 123 << endl; // output = '*******123' cout << fixed << setprecision(3) << 123456.12568 << endl; output 123456.126 (note the rounding)


My opinion

Use printf for formatted output--C++'s formatting options are too clunky.