EXPLORATION 8

image

Formatted Output

In Exploration 4, you used tab characters to line up output neatly. Tabs are useful but crude. This Exploration introduces some of the features that C++ offers to format output nicely, such as setting the alignment, padding, and width of output fields.

The Problem

This Exploration begins a little differently. Instead of reading a program and answering questions about it, you must write your own program to solve a problem. The task is to print a table of squares and cubes (the mathematical variety, not the geometrical shapes) for integers from 1 to 20. The output of the program should look something like the following:

 N    N^2    N^3
 1     1      1
 2     4      8
 3     9     27
 4    16     64
 5    25    125
 6    36    216
 7    49    343
 8    64    512
 9    81    729
10   100   1000
11   121   1331
12   144   1728
13   169   2197
14   196   2744
15   225   3375
16   256   4096
17   289   4913
18   324   5832
19   361   6859
20   400   8000

To help you get started, Listing 8-1 gives you a skeleton program. You need only fill in the loop body.

Listing 8-1.  Print a Table of Squares and Cubes

#include <iomanip>
#include <iostream>
 
int main()
{
  std::cout << " N   N^2    N^3 ";
  for (int i{1}; i != 21; ++i)
  {
    // write the loop body here
  }
}

This is a trick problem, so don’t worry if you had difficulties. The point of this exercise is to demonstrate how difficult formatted output actually is. If you’ve learned that much, you successfully completed this exercise, even if you didn’t finish the program. Perhaps you tried using tab characters at first, but that aligns the numbers on the left.

N       N^2     N^3
1       1       1
2       4       8
3       9       27
4       16      64
5       25      125
6       36      216
7       49      343
8       64      512
9       81      729
10      100     1000

Left-alignment is not the way we usually write numbers. Tradition dictates that numbers should align to the right (or on decimal points, when applicable—more on that in the related section, “Alignment,” later in this Exploration). Right-aligned numbers are easier to read.

C++ offers some simple but powerful techniques to format output. To format the table of powers, you have to define a field for each column. A field has a width, a fill character, and an alignment. The following sections explain these concepts in depth.

Field Width

Before exploring how you would specify alignment, first you must know how to set the width of an output field. I gave you a hint in Listing 8-1. What is the hint?

_____________________________________________________________

The first line of the program is #include <iomanip>, which you have not seen before. This header declares some useful tools, including std::setw(), which sets the minimum width of an output field. For example, to print a number so that it occupies at least three character positions, call std::setw(3). If the number requires more space than that—say the value is 314159—the actual output will take up as much space as needed. In this case, the spacing turned out to be six character positions.

To use setw, call the function as part of an output statement. The statement looks like you are trying to print setw, but in fact, nothing is printed, and all you are doing is manipulating the state of the output stream. That’s why setw is called an I/O manipulator. The <iomanip> header declares several manipulators, which you will learn about in due course.

Listing 8-2 shows the table of powers program, using setw to set the width of each field in the table.

Listing 8-2.  Printing a Table of Powers the Right Way

#include <iomanip>
#include <iostream>
 
int main()
{
  std::cout << " N   N^2    N^3 ";
  for (int i{1}; i != 21; ++i)
    std::cout << std::setw(2) << i
              << std::setw(6) << i*i
              << std::setw(7) << i*i*i
              << ' ';
}

The first column of the table requires two positions, to accommodate numbers up to 20. The second column needs some space between columns and room for numbers up to 400; setw(6) uses three spaces between the N and the N^2 columns and three character positions for the number. The final column also uses three spaces between columns and four character positions, to allow numbers up to 8000.

The default field width is zero, which means everything you print takes up the exact amount of space it needs, no more, no less.

After printing one item, the field width automatically resets to zero. For example, if you wanted to use a uniform column width of six for the entire table, you could not call setw(6) once and leave it at that. Instead, you must call setw(6) before each output operation, as follows:

    std::cout << std::setw(6) << i
              << std::setw(6) << i*i
              << std::setw(6) << i*i*i
              << ' ';

Fill Character

By default, values are padded, or filled, with space characters (' '). You can set the fill character to be any that you choose, such as zero ('0') or an asterisk ('*'). Listing 8-3 shows a fanciful use of both fill characters in a program that prints a check.

Listing 8-3.  Using Alternative Fill Characters

#include <iomanip>
#include <iostream>
 
int main()
{
  using namespace std;
 
  int day{14};
  int month{3};
  int year{2006};
  int dollars{42};
  int cents{7};
 
  // Print date in USA order. Later in the book, you will learn how to
  // handle internationalization.
  cout << "Date: " << setfill('0') << setw(2) << month
                            << '/' << setw(2) << day
                            << '/' << setw(2) << year << ' ';
  cout << "Pay to the order of: CASH ";
  cout << "The amount of $" << setfill('*') << setw(8) << dollars << '.'
                            << setfill('0') << setw(2) << cents << ' ';
}

Notice that unlike setw, setfill is sticky. That is, the output stream remembers the fill character and uses that character for all output fields until you set a different fill character.

std Prefix

Another new feature in Listing 8-3 is the declaration using namespace std;. All those std:: prefixes can sometimes make the code hard to read. The important parts of the names become lost in the clutter. By starting your program with using namespace std;, you are instructing the compiler to treat names that it doesn’t recognize as though they began with std::.

As the keyword indicates, std is called a namespace. Almost every name in the standard library is part of the std namespace. You are not allowed to add anything to the std namespace, nor are any third-party library vendors. Thus, if you see std::, you know that what follows is part of the standard library (so you can look it up in any reliable reference). More important, you know that most names you invent in your own program will not conflict with any name in the standard library, and vice versa. Namespaces keep your names separate from the standard library names. Later in this book, you will learn to create your own namespaces, which help organize libraries and manage large applications.

On the other hand, using namespace std; is a dangerous declaration, and one I use sparingly. Without the std:: qualifier before every standard library name, you have opened the door to confusion. Imagine, for example, if your program defines a variable named cout or setw. The compiler has strict rules for interpreting names and would not be confused at all, but the human reader certainly would be. It is always best to avoid names that collide with those in the standard library, with or without using namespace std;.

Alignment

C++ lets you align output fields to the right or the left. If you want to center a number, you are on your own. To force the alignment to be left or right, use the left and right manipulators, which you get for free when you include <iostream>. (The only time you need <iomanip> is when you want to use manipulators that require additional information, such as setw and setfill.)

The default alignment is to the right, which might strike you as odd. After all, the first attempt at using tab characters to align the table columns produced left-aligned values. As far as C++ is concerned, however, it knows nothing about your table. Alignment is within a field. The setw manipulator specifies the width, and the alignment determines whether the fill characters are added on the right (left-alignment) or on the left (right-alignment). The output stream has no memory of other values it may have printed earlier (such as on the previous line). So, for example, if you want to align a column of numbers on their decimal points, you must do that by hand (or ensure that every value in the column has the same number of digits after the decimal point).

Exploring Formatting

Now that you know the rudiments of formatting output fields, it is time to explore a little and help you develop a thorough understanding of how field width, fill character, and alignment interact. Read the program in Listing 8-4 and predict its output.

Listing 8-4.  Exploring Field Width, Fill Character, and Alignment

#include <iomanip>
#include <iostream>
 
int main()
{
  using namespace std;
  cout << '|' << setfill('*') << setw(6) <<  1234 << '|' << ' ';
  cout << '|' << left <<         setw(6) <<  1234 << '|' << ' ';
  cout << '|' <<                 setw(6) << -1234 << '|' << ' ';
  cout << '|' << right <<        setw(6) << -1234 << '|' << ' ';
}

What do you expect as the output from Listing 8-4?

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

_____________________________________________________________

Now write a program that will produce the following output. Don’t cheat and simply print a long string. Instead, print only integers and newlines, throwing in the field width, fill character, and alignment manipulators you require to achieve the desired output.

000042
420000
42
-42-

Lots of different programs can achieve the same goal. My program, shown in Listing 8-5, is only one possibility of many.

Listing 8-5.  Program to Produce Formatted Output

#include <iomanip>
#include <iostream>
 
int main()
{
  using namespace std;
 
  cout << setfill('0') << setw(6) << 42 << ' ';
  cout << left         << setw(6) << 42 << ' ';
  cout << 42 << ' ';
  cout << setfill('-') << setw(4) << -42 << ' ';
}

The manipulators that take arguments, such as setw and setfill, are declared in <iomanip>. The manipulators without arguments, such as left and right, are declared in <iostream>. If you can’t remember, include both headers. If you include a header that you don’t really need, you might see a slightly slower compilation time, but no other ill effects will befall you.

I’M LYING TO YOU

The left and boolalpha manipulators are not declared in <iostream>. I lied to you. They are actually declared in <ios>. But <iostream> contains #include <ios>, so you get everything in <ios> automatically when you include <iostream>.

I’ve been lying to you for some time. The input operator (>>) is actually declared in <istream>, and the output operator (<<) is declared in <ostream>. As with <ios>, the <iostream> header always includes <istream> and <ostream>. Thus, you can include <iostream> and get all the headers you need for typical input and output. Other headers, such as <iomanip>, are less commonly used, so they are not part of <iostream>.

So I wasn’t really lying to you, just waiting until you could handle the truth.

Alternative Syntax

I like to use manipulators, because they are concise, clear, and easy to employ. You can also apply functions to the output stream object, using the dot operator (.). For example, to set the fill character, you can call std::cout.fill('*'). The fill function is called a member function, because it is a member of the output stream’s type. You cannot apply it to any other kind of object. Only some types have member functions, and each type defines the member functions that it allows. A large part of any C++ library reference is taken up with the various types and their member functions. (The member functions of an output stream are declared in <ostream>, along with the output operators. An input stream’s member functions are declared in <istream>. Both of these headers are included automatically when you include <iostream>.)

When setting sticky properties, such as fill character or alignment, you might prefer using member functions instead of manipulators. You can also use member functions to query the current fill character, alignment and other flags, and field width—something you can’t do with manipulators.

The member function syntax uses the stream object, a dot (.), and the function call, e.g., cout.fill('0'). Setting the alignment is a little more complicated. Listing 8-6 shows the same program as Listing 8-5 but uses member functions instead of manipulators.

Listing 8-6.  A Copy of Listing 8-5, but Using Member Functions

#include <iostream>
 
int main()
{
  using namespace std;
 
  cout.fill('0'),
  cout.width(6);
  cout << 42 << ' ';
  cout.setf(ios_base::left, ios_base::adjustfield);
  cout.width(6);
  cout << 42 << ' ';
  cout << 42 << ' ';
  cout.fill('-'),
  cout.width(4);
  cout << -42 << ' ';
}

To query the current fill character, call cout.fill(). That’s the same function name you use to set the fill character, but when you call the function with no arguments, it returns the current fill character. Similarly, cout.width() returns the current field width. Obtaining the flags is slightly different. You call setf to set flags, such as the alignment, but you call flags() to return the current flags. The details are not important at this time, but if you’re curious, consult any relevant library reference.

On Your Own

Now it is time for you to write a program from scratch. Feel free to look at other programs, to make sure you have all the necessary parts. Write this program to produce a multiplication table for the integers from 1 to 10, inclusive, as follows:

   *|   1   2   3   4   5   6   7   8   9  10
----+----------------------------------------
   1|   1   2   3   4   5   6   7   8   9  10
   2|   2   4   6   8  10  12  14  16  18  20
   3|   3   6   9  12  15  18  21  24  27  30
   4|   4   8  12  16  20  24  28  32  36  40
   5|   5  10  15  20  25  30  35  40  45  50
   6|   6  12  18  24  30  36  42  48  54  60
   7|   7  14  21  28  35  42  49  56  63  70
   8|   8  16  24  32  40  48  56  64  72  80
   9|   9  18  27  36  45  54  63  72  81  90
  10|  10  20  30  40  50  60  70  80  90 100

After you finish your program and have made sure it produces the correct output, compare your program with mine, which is shown in Listing 8-7.

Listing 8-7.  Printing a Multiplication Table

#include <iomanip>
#include <iostream>
 
int main()
{
  using namespace std;
 
  int const low{1};        ///< Minimum value for the table
  int const high{10};      ///< Maximum value for the table
  int const colwidth{4};   ///< Fixed width for all columns
 
  // All numbers must be right-aligned.
  cout << right;
 
  // First print the header.
  cout << setw(colwidth) << '*'
       << '|';
  for (int i{low}; i <= high; i = i + 1)
    cout << setw(colwidth) << i;
  cout << ' ';
 
  // Print the table rule by using the fill character.
  cout << setfill('-')
       << setw(colwidth) << ""                    // one column's worth of "-"
       << '+'                                     // the vert. & horz. intersection
       << setw((high-low+1) * colwidth) << ""     // the rest of the line
       << ' ';
 
  // Reset the fill character.
  cout << setfill(' '),
 
  // For each row...
  for (int row{low}; row <= high; row = row + 1)
  {
    cout << setw(colwidth) << row << '|';
    // Print all the columns.
    for (int col{low}; col <= high; col = col + 1)
      cout << setw(colwidth) << row * col;
    cout << ' ';
  }
}

My guess is that you wrote your program a little differently from how I wrote mine, or perhaps you wrote it very differently. That’s okay. Most likely, you used a hard-coded string for the table rule (the line that separates the header from the table), or perhaps you used a for loop. I used I/O formatting, just to show you what is possible. Printing an empty string with a nonzero field width is a quick and easy way to print a repetition of a single character.

Another new feature I threw in for good measure is the const keyword. Use this keyword in a definition to define the object as a constant instead of a variable. The compiler makes sure you do not accidentally assign anything to the object. As you know, named constants make programs easier to read and understand than littering the source code with numbers.

Loops sure are fun! What data structure do you think of first when you think of loops? I hope you picked arrays, because that is the subject of the next Exploration.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset