The Essentials of C++, David hunter

Chapter 11. Input and Output

11.1 Class iostream

  • Input and output facilities are provided by the functions and objects defined within the file “iostream.h”, included with every C++ implementation. Programmers may also opt to perform I/O using the C library package, “stdio.h”, but his usage is archaic and is not discussed here.

  • The iostream package provides access to standard input via the object cin, to sstandard output via the object cout, and to the standard error device via cerr. The symbol « is used to write to cout or cerr and the symbol » is used to read from cin. There use is illustreaed below.

cout << "Please enter two integer values \n";
cin >> i >> j;
cout << "You entered " << i << ' ' << j << " \nTheir sum is " << i + j << '\n';
  • The object cin skips over all white space (blanks, tabs, and newlines) when performing an » operation. When reading an integer, it consumes a sign, if present, then consumes characters until a non-digit is encountered. Real numbers are read in an analogous manner. Some examples are presented in the table on the next page. The table assumes i is an integer, x is a float and c is a character.

  • The object cin, cout, and cerr are all instances of the class iostream. In addition to the operators « and », there are a number of methods defined for iostream objects. These are summarized in the table on the next page.

  • The width method can be used to set the output field width for cout. When the width is zero, as it is by default, values are printed with no leading spaces. If the width is set to a number largr than the number of digits to be written, the value is padded with the current fill character. The fill character is a space by default and may be set using the fill method. Note that when the width is set using the width method it affects only the next output item. Afetrward, the width automatically resets to zero.

Table 11.1
Examples Showing Effect of << witn cin
Input source Effect of cin » i » c » x
123 a123 i = 123
c = ‘a’
x = 123.0
123
a
123.5
i = 123
c = ‘a’
x = 123.5
+123 +123a.123 i = 123
c = ‘a’
x = 0.123
123aa123 i = 123
c = ‘a’
x is undefined due to unexpected input
a123 c123 all variables undefined due to unexpected input when reading i
Table 11.2
Methods for iostream Objects
function description
char get() Read and return the next character, including whitespace characters
put(char c) Output c
read(char s[], int size) Read size number of characters and put them in s
write(char s[], int size) Write size number of character from s
get_line(char s[], int n, char delim = ‘\n’); Read at most n-1 characters until the delim character or eof is encountered. Put them in s, followed by the ‘\0’ character
width(int i) Set the output field width of obj to i
precision(int i) Set the output precision of obj to i. This is the number of digits to write to the right of the decimal point.
char fill(char c) Set the fill character to c
int eof() Return true(1) if obj has attempted to read past the end of the file
int bad() Return true if an illegal operation has been requested
int fail() Return true if obj.bad() or if an operation has failed
int good() Return true if none of the previous error conditions apply
  • Precision refers to the number of digits to print to the right of the decimal point. It is relevant only for float or double values. A number will be rounded to fit within the specified precision.

  • The method fail() is useful for testing if an operation was successful. In the example below it is used to determine if an integer was input properly.

cout << "Enter number of students";
cin >> n;
while (cin.fail())
{
    cout >> "Try again please:";
    cin >> n;
}
  • In the example below, eof(), get(), and put() are used to copy an input source. Note that eof() does not become true until after a “read past end of file” operation has been attempted.
char c = cin.get();
while (! cin.eof())
{
    cout.put();
    c = cin.get();
}

11.2 Overloading the Stream Operators

  • It is common practice for C++ programmers to overload the « and » operators when creating new data types. For example, given the student_record structure presented earlier, one would like to be able to produce code like that shown below.
student_record r = {1234, 's', 3.25};
// ...
cout << s;
  • Doing so requires that the « operators be overloaded to work with student records. The technique is shown below.
iostream& operator << (iostream& out, student_record& s)
{
    cout << s.id << '\t' << s.classification << '\t' << s.gpa;
    return out;
}
  • When overloaded, the iostream operators « and » should always return a reference to their left operand. This, and the fact they they are left associative, ensures that they can continue to be used in the manner of a stream. That is, given cout « a « b « c, left associativity ensures that the expression is evaluated as (((cout « a ) « b) « c), and evaluation contines in the desired manner.

11.3 Setting Format Options

  • Detailed formatting options for iostream objects can be controlled using the setf method. This method modifies a set of bit-flags (stored within a long variable) used by iostream objects to maintain the current state of their formatting options. The class ios defines constants that can be used in conjunction with setf to access these bit fiels. These constants are presented in the table on the next page.

  • When called with one parameter, setf has the effect of turning “on” the specifieid formatting option. When called with two parameters, the options speicified by the second parameter are turned off, then the options specified by the first parameter are turned on. For example, the statement cout.setf(ios::fixed, ios::floatfield) causes the ios::scientific flag to be turned off and the ios::fixed flat to be turned on. All reals are subsequently displayed in fixed point notation. The second argument is necessary to ensure that ios::scientific will be off, since both this and ios::fixed cannt be on at the same time. Similarly, the statement cout.setf(ios::right, ios::adjustfield) would be used to set output formatting to right justification. Note that the basefield flags affect only integer output.

Table 11.3
Format Flag Constants
function description
ios::skipws skip whitespace
ios::left left justify
ios::right right justify
ios::internal pad after sign or base indicator
ios::dec decimal output
ios::oct octal output
ios::hex hex output
ios::showbase show base of output
ios::showpoint force display of decimal point(floats only)
ios::uppercase always display in upper case
ios::showpos add “+” to front of positive numbers
ios::scientific displays floats in scientific notation
ios::fixed display floats in fixed point notation
ios::unitbuf flush all streams after insertion
ios::stdio flush stdout, stderr after insertion
ios::basefield This is equal to : ios::dec|ios::oct|ios::hex
ios::adjustfield This is equal to: ios::left|ios::right||ios::internal
ios::floatfield This is equal to: ios::scientific | ios::fixed
  • Format flags can be combined using bitwise OR to create groups of formatting options. Thus, the call cout.setf(ios::fixed | ios::showpos | ios::uppercase, ios::floatfield) turns off ios::scientific and turns on ios::fixed, ios::showpos, and ios::uppercase.

  • Because most I/O operations are buffered it is not always possible to rely upon the fact that, for example, a message will be displayed exactly when it is written to cout. Instead, on many systems, output is not written to the appropriate device until the output buffer is full. The ios::unitbuf flag allows the programmer to specify that all I/O bufferes are to be emptied as soon as an item is inserted into them. The flag ios::stdio restricts this to only the cout and cerr buffers.

11. 4 Formatting and Using I/O Manipulators

  • In addition to using setf, formatting options can be controlled by including I/O “manipulator” in a stream. These are defined in the standard header file “iomanip.h” and summarized in the table below. Manipulators have the same effects as their corresponding iostream methods. An example illustrating their use is given in the following table:
Table 11.4
I/O Manipulators Defined in "iomanip.h"
function description
oct Set output base to octal
dec Set output base to decimal
hex Set output base to hex
endl send ‘\n’ and flush stream
ends send ‘\0’ and flush stream
flush flush stream
ws ignore whitespace
setbase(int) set output base to specified integer(8, 10 or 16)
setfill(char) set fill character
setw(int) set output width
setprecision(int) set output precision
resetiosflags(long) turn off specified ios flags
setiosflags(long) turn on specified ios flags
#include <iostream.h>
#include <iomanip.h>

void main()
{
    cout << resetiosflags(ios::floatfield | ios::adjustfield) 
        << setiosflags(ios::fixed | ios::right)
        << setw(10) << set precision(2)
        // output will now be right justified, floats printed in fixed fields
        << 123.456 << endl
        // output is: 123.46
        << hex << 16 << endl
        // output is: 10
        << setw(10) <<setfill('#') << 123 << endl
        // output is: ########7b
        << 123.456
        // output is: 123.46
}
  • Program illustrating use of I/O manipulators

11.5 File Classes

  • The header file “fstream.h” defines three file classes: ifstream, ofstream, and fstream. These are used for read only files, write only files, and read/write files respectively. The open and close methods are used to associate and disassociate physical files with file objects. This is illustreated by the example below, which copies one file to another.
ifstream infile;
ofstream outfile;
infile.open("infile.txt");
outfile.open("outfile.txt");
char c = infile.get();
while (!infile.eof())
{
    outfile.put(c);
    c = infile.get();
};
infile.close();
outfile.close();
  • Alternately, the file name for a file object can be specified as an argument to its constructor. Thus, one could also have written:
ifstream infile("infile.txt");
ofstream outfile("outfile.txt");
char c = infile.get();
// ...
  • Objects of class fstream may be used as either read files, write files, or read/write files. When creating an fstream object, one of the three file modes must be specified using the constants ios::in, ios::out, or ios::in | ios::out, as illustrated below.
fstream read_only_file("infile.txt", ios::in);
fstream write_only_file("outfile.txt", ios::out);
fstream read_write_file("update.txt", ios::in | ios::out);
  • One may also open a file in “append” mode by specifying ios::app.

  • The file classes maintain a file position marker that indicates the character position where the next I/O operation is to be performed. The methods seekp and seekg are used to modify this position marker for write and read files respectively. The methods take two parameters. The first specifies an offset and the second specifies a starting point. Usually the second parameter is one of the values ios::beg, ios::end, ios::cur, which represent the beginning of the file, end of the file, and the current file position, respectively. Thus, given that infile is an object of type ifstream the call infile.seekg(-1, ios::cur) has the effect of moving the file position marker back one character. Similarly, give that outfile is an ofstream object, the statement outfile.seekp(0, ios::beg) places the file position marker at the beginning of the file associated with outfile. The methods tellp() and tellg() can be used to determine the current file position marker specified as an offset from ios::beg.

  • The program below modifies a file by replacing all periods with exclamation marks.

#include <iostream.h>
#include <fstream.h>

void main()
{
    char fname[32];
    cout << "Enter file name: ";
    cin >> fname;
    fstream f(fname, ios::in | ios::out);
    char c = f.get();
    while (! f.eof())
    {
        if (c == '.')
        {
            f.seekp(-1, ios::cur);
            f.put('!');
        };
        c = f.get();
    };
}

11.6 Binary I/O

  • The preceding examples operated upon files by reading and writing individual characters. It is often convenient to read or write an entire sequence of bytes, for example a vehicle object or a student record, in one operation. The read and write methods are used for this purpose. These methods take two parameters: the first is an address specifying the location of the object to be read or written and the second is an integer indicating the size of the object. The address parameter should be specified using the type cast unsigned char *. For example, the sequence below writes the binary image of a vehicle object into a file.
ofstream ofile("vehicles");
vehicle v(2000);
ofile.write((unsigned char *) &f, sizeof(vehicle));