11. Customizing Input and Output

"Keep it simple: as simple as possible, but no simpler." -- Albert Einstein

11.1 Regularity and irregularity

The iostream library which is the input/output part of the ISO C++ standard library. This library provides a unified and extensible framework for I/O of text. In this chapter we will learn number of ways in which we can tailor I/O to our needs

As programmers we naturally prefer regularity: handle everything in a uniform fashion! It is simpler and pleases our engineering sense. But our users often have strong preferences for very irregular arrangements of their data. We have to balance these two factors.

11.2 Output formatting

People care a lot about the minor details of output. Our aim as a programmer is to keep output as clear and close to user's expectation as possible.

11.2.1 Integer output

Integer values can be output as octal, decimal, and hexadecimal. Most output uses decimal. Below code represents output 1234 (decimal) to decimal, hexadecimal, and octal:

                        cout << 1234 << "\t(decimal)\n"
                            << hex << 1234 << "\t(hexadecimal)\n"
                            << oct << 1234 << "\t(octal)\n";
                        

Output for this is:

                        1234 (decimal)
                        4d2 (hexadecimal)
                        2322 (octal)
                        
11.2.2 Integer input

By default, >> assumes that numbers use the decimal notation, but it can also read hexadecimal or octal integers:

                        int a;
                        int b;
                        int c;
                        int d;
                        cin >> a >> hex >> b >> oct >> c >> d;
                        cout << a << '\t' << b << '\t' << c << '\t' << d << '\n';
                        

If you type in:
1234 4d2 2322 2322
this will print:
1234 1234 1234 1234

11.2.3 Floating-point output

This is very important in case you are dealing with scientific computation. These are handled using iostream manipulator in a manner very similar to that of integer values:

                        cout << 1234.56789 << "\t\t(defaultfloat)\n"   // \t\t to line up columns
                            << fixed << 1234.56789 << "\t(fixed)\n"
                            << scientific << 1234.56789 << "\t(scientific)\n";
                        

This prints:

                        1234.57         (general)
                        1234.567890     (fixed)
                        1.234568e+003   (scientific)
                        
11.2.4 Precision

By default the precision for floating point numbers is six total digits using the defaultfloat format. The chooses the most appropriate format and the number is rounded to give the best approximation that can be printed using only six digits.

A programmer can set the precision using the manipulator setprecision(). For example:

                        cout << 1234.56789 << '\t'
                            << fixed << 1234.56789 << '\t'
                            << scientific << 1234.56789 << '\n';
                        cout << defaultfloat << setprecision(5)
                            << 1234.56789 << '\t'
                            << fixed << 1234.56789 << '\t'
                            << scientific << 1234.56789 << '\n';
                        cout << defaultfloat << setprecision(8)
                            << 1234.56789 << '\t'
                            << fixed << 1234.56789 << '\t'
                            << scientific << 1234.56789 << '\n';
                        

This prints (note the rounding):

                        1234.57     1234.567890     1.234568e+003
                        1234.6 1234.56789   1.23457e+003
                        1234.5679   1234.56789000   1.23456789e+003
                        
11.2.5 Fields

Using scientific and fixed formats, a programmer can control exactly how much space a value takes up on output. That’s clearly useful for printing tables, etc. “set field width” manipulator setw() can be used to specify exactly how many character positions an integer value or string value will occupy. Example:

                        cout << 123456
                            <<'|'<< setw(4) << 123456 << '|'
                            << setw(8) << 123456 << '|'
                            << 123456 << "|\n";
                        

This prints:

                        123456|123456| 123456|123456|
                        
Drill

We'd probably like our calculator to give high precision answers: use set precision to up the number of output digits.

11.3 File opening and positioning

In C++, a file is is an abstraction of what the operating system provides. A file is sequence of bytes numbered from 0 upwards. The question is how to access these bytes. The simplest example of this is that if we open an istream for a file, we can read from the file, whereas if we open a file with an ostream, we can write to it.

11.3.1 File open modes

A file can be opened in several modes. By default, ifstream opens it for reading and ofstream opens for writing. These take care of the most common needs but there are several other alternatives given in the book. A file mode is optionally specified after the name of the file. For example:

ofstream of1 {name1};   //defaults to ios_base::out
ifstream if1 {name2};   //defaults to ios_base::in

ofstream ofs {name, ios_base::app}; // app means "append"
fstream fs {"myfile",ios_base::in|ios_base::out};//both in and out
                        
11.3.2 Binary files

We can tell our IO streams to output binary data, i.e., represent an int not as a sequence of chars but as 0100100 etc. by using ios_base::binary.

11.3.3 Positioning in files

We can also go to a specific position in a file for I/O. Advanced stuff we won't do, but it is good to know we can! (We might use this if we were writing a new database, for instance, or editing a movie.)

11.4 String streams

A string can be used as a source of an istream or the target for an ostream. An istream that reads from a string is called an istringstream and an ostream that stores characters written to it in a string is called an ostringstream. We can use an istringstream for extracting numeric values from a string:

double str_to_double(string s)
    // if possible, convert characters in s to floating-point value
{
    istringstream is {s};      // make a stream so that we can read from s
    double d;
    is >> d;
    if (!is) error("double format error: ",s);
    return d;
}
double d1 = str_to_double("12.4");
double d2 = str_to_double("1.34e–3");
double d3 = str_to_double("twelve point three");
                    
11.5 Line-oriented input

If we want to read everything on a line at once and want to decide the format later. This can be done using the function getline(). For example:

string name;
getline(cin,name);      // input: Dennis Ritchie
cout << name << '\n';    // output: Dennis Ritchie
                    

>> reads only until it encounters whitespace. If we use >> instead of getline() the output will be 'Dennis'.

11.6 Character classification

Sometimes we go down to a level of abstraction and read individual characters. This is certainly more work but when we do this, we have a full control over what we are doing. Consider tokenizing this expression: 1+4*x<=y/z*5 to be separated into the eleven tokens.

We could use >> to read numbers. but trying to read identifiers as strings would cause x<=y to to be read as one string. Similarly it will read z* as one string. So, instead we can write something like this:

for (char ch; cin.get(ch); ) {
    if (isspace(ch)) {  // if ch is whitespace
        // do nothing (i.e., skip whitespace)
    }
    if (isdigit(ch)) {
        // read a number
    }
    else if (isalpha(ch)) {
        // read an identifier
    }
    else {
        // deal with operators
    }
}
                    
11.7 Using nonstandard separators

We can define our own stream type to recognize different separators than the default, e.g., to recognize something other than whitespace as separating strings. (Think of trying to extract words from a stream of text: we don't want "To be or not to be, that is the question" to return "be," as a word!) This is difficult programming, and beyond what we can tackle in this course, but the example from the book is worth studying.)

11.8 And there is so much more

The details of I/O seem infinite. They probably are, since they are limited only by human inventiveness and capriciousness. For example, we have not considered the complexity implied by natural languages.

Test Yourself!
  1. Which of the following is an integer output manipulator?
    1. all mentioned
    2. showbase
    3. hex
    4. noshowbase
  2. Which of the following is not a basic floating-point output manipulator?
    1. defaultfloat
    2. showbase
    3. scientific
    4. fixed
  3. What does isalnum(c) do?
    1. is c a letter or a decimal digit?
    2. is c printable?
    3. is c a decimal digit?
    4. is c whitespace?
Answers

1. a; 2. b; 3. a;

Drill