C++ Output Formatting

Output Formatting

Many students complain that C++ streams are difficult to use for output, when compared to the older C methods. My theory is that, if the situation were reversed and students were required to move from C++ stream formatting back to the old methods, the complaints would be louder.
The biggest single advantage to the stream methods is they are type-safe. If you change a variable's type, the subsequent stream operations using that variable will either automatically accommodate the change, or will indicate an incompatibility at compile time. In older C code, any number of difficult-to-detect bugs result from either incorrectly specifying a variable's type, or changing the variable's type and not remembering all the places where the specifiers need to be changed.
Please remember ― this is a very basic introduction. Like this entire tutorial, this page is not a comprehensive guide to stream formatting ― it just answers the most commonly heard student questions. A quality textbook should be acquired to serve more general needs.
1. Field Width
Setting field width is very simple. For each variable, simply precede it with "setw(n)". Like this:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
	const int max = 12;
	const int width = 6;
	for(int row = 1;row <= max;row++) {
		for(int col = 1;col <= max;col++) {
			cout << setw(width) << row * col;
		}
		cout << endl;
	}
	return 0;
}
    
Notice how "setw(n)" controls the field width, so each number is printed inside a field that stays the same width regardless of the width of the number itself.
2. Justification in Field
Now that you have selected a field, you may wish to decide which side of this field to occupy. As you may imagine, the choices are left and right. Here is an example of switching justification line-by-line:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
	const int max = 12;
	const int width = 6;
	for(int row = 1;row <= max;row++) {
		if(row % 2) {
			cout << setiosflags(ios::left);
		}
		else {
			cout << resetiosflags(ios::left);
		}
		for(int col = 1;col <= max;col++) {
			cout << setw(width) << row * col;
		}
		cout << endl;
	}
	return 0;
}
    
Because numbers are by default right-justified, in this case I only need to set and unset the "ios::left" flag. In other situations, you may want to use "ios::right" in code similar to this.
Unfortunately, there are two flags, "ios::left" and "ios::right". This leads to an obvious confusion about which flag is active. In some ambiguous cases, you may have to do this:
	cout << setiosflags(ios::right);
	cout << resetiosflags(ios::left);
    
3. Controlling Precision
"Precision" in this context means the number of decimal places in a floating-point variable. Compile and run the following program:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
	double x = 800000.0/81.0;
	cout << setprecision(2) << x;
	return 0;
}
    
This program isn't broken ― you asked for two places, it printed two places. If instead you want two decimal places (positions to the right of the decimal point), you must first choose the fixed-point format. Like this:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
	double x = 800000.0/81.0;
	cout << setiosflags(ios::fixed) << setprecision(2) << x;
	return 0;
}
    
This is a very common student problem ― the default C++ stream behavior for setprecision(n) is to apply the specification to the entire number, not the fractional part. If this is not what you want, set the fixed-point format first.
There is something very important to know about "setprecision(n)". If you choose a precision this way, the displayed number will be rounded off appropriately. Experiment with the following program:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
	double x = 2.0/3.0;
	cout << setiosflags(ios::fixed) << setprecision(4) << x;
	return 0;
}
    
The result (.6667) is an appropriate four-digit representation of the repeating decimal fraction 2/3. If you do not want this behavior, you will need to think of a different method for display. In most cases, the default behavior is the correct one.
4. Leading zeros
The C++ stream classes provide an easy way to choose a character to fill the leading spaces in a number (and this program has a deliberate bug):

#include <iostream>
#include <iomanip>
using namespace std;
void showDate(int m, int d, int y)
{
	cout << setfill('0');
	// this line has an error
	cout << setw(2) << m << '/' << d << '/' << y << endl;
}
int main()
{
	showDate(1,1,1999);
	return 0;
}
    
The bug (no leading character in the month) is caused by the fact that "setw(n)" is volatile. "setw(n)" only works for a single subsequent variable. You must apply "setw(n)" for each variable. Do it this way:
#include <iostream>
#include <iomanip>
using namespace std;
void showDate(int m, int d, int y)
{
	cout << setfill('0');
	cout << setw(2) << m << '/'
	<< setw(2) << d << '/'
	<< setw(4) << y << endl;
}
int main()
{
	showDate(1,1,1999);
	return 0;
}
    
5. Number Bases other than 10
For base 8 and base 16, this is an easy problem to solve:
#include <iostream>
using namespace std;
int main()
{
	unsigned long x = 64206;
	cout << x
	<< " in base 8 is \"" << oct << x << "\"" 
	<< " and in base 16 is \"" << hex << x << "\"" << endl;
	return 0;
}
    
By the way, this stream formatting for different bases also works for input:
	int x;
	cin >> hex >> x;
    
There is no general arbitrary-base display solution built into the language. Here is an example of a solution:
#include <iostream>
#include <string>
using namespace std;
string convBase(unsigned long v, long base)
{
	string digits = "0123456789abcdef";
	string result;
	if((base < 2) || (base > 16)) {
		result = "Error: base out of range.";
	}
	else {
		do {
			result = digits[v % base] + result;
			v /= base;
		}
		while(v);
	}
	return result;
}
int main()
{
	unsigned long x = 64206;
	cout << "Hex:     " << convBase(x,16) << endl;
	cout << "Decimal: " << convBase(x,10) << endl;
	cout << "Octal:   " << convBase(x,8) << endl;
	cout << "Binary:  " << convBase(x,2) << endl;
	cout << "Test:    " << convBase(x,32) << endl;
	return 0;
}
    
6. Currency
This is an advanced topic, because displaying currency is more complex than it may appear at first sight. There is an advanced C++ feature called "locale" that can handle this problem in a powerful way, but it is not enabled on many compilers (and not yet on the very common compiler I have chosen for this tutorial). Here is a way to display currency:
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
void showCurrency(double dv, int width = 14)
{
	const string radix = ".";
	const string thousands = ",";
	const string unit = "$";
	unsigned long v = (unsigned long) ((dv * 100.0) + .5);
	string fmt,digit;
	int i = -2;
	do {
		if(i == 0) {
			fmt = radix + fmt;
		}
		if((i > 0) && (!(i % 3))) {
			fmt = thousands + fmt;
		}
		digit = (v % 10) + '0';
		fmt = digit + fmt;
		v /= 10;
		i++;
	}
	while((v) || (i < 1));
	cout << unit << setw(width) << fmt.c_str() << endl;
}
int main()
{
	double x = 12345678.90;
	while(x > .001) {
		showCurrency(x);
		x /= 10.0;
	}
	return 0;
}
    
This approach has many drawbacks. I have converted the double to an unsigned long before beginning my algorithm, which limits the range of possible currency amounts. Try experimenting with this code ― test the various statements to see how they work. And if you are ambitious, see if you can make a double work directly, without converting to an unsigned long first (and good luck :) ).
Also notice the width argument to "showCurrency()". Notice it is given a default value, which makes it unnecessary to even specify a value when the function is called. This default argument syntax is a standard feature of C++. If you want a width other than 14, you may include your own value in your call to the function. Like this:
		showCurrency(x, 16);

你可能感兴趣的:(C++,职场,休闲)