这篇文章主要讲解如何在C++中使用cout进行高级的格式化输出操作,包括数字的各种计数法(精度)输出,左或右对齐,大小写等等。通过本文,您可以完全脱离scanf/printf,仅使用cout来完成一切需要的格式化输入输出功能(从非性能的角度而言)。更进一步而言,您还可以在
cout是STL库提供的一个iostream实例,拥有ios_base基类的全部函数和成员数据。进行格式化操作可以直接利用setf/unsetf函数和flags函数。cout维护一个当前的格式状态,setf/unsetf函数是在当前的格式状态上追加或删除指定的格式,而flags则是将当前格式状态全部替换为指定的格式。cout为这个函数提供了如下参数(可选格式):
以上每一种格式都占用独立的一位,因此可以用“|”(位或)运算符组合使用。调用setf/unsetf或flags设置格式一般按如下方式进行:
1
2
|
cout.setf(ios::right | ios::hex);
//设置16进制右对齐
cout.setf(ios::right, ios::adjustfield);
//取消其它对齐,设置为右对齐
|
setf可接受一个或两个参数,一个参数的版本为设置指定的格式,两个参数的版本中,后一个参数指定了删除的格式。三个已定义的组合格式为:
设置格式之后,下面所有使用cout进行的输出都会按照指定的格式状态执行。但是如果在一次输出过程中需要混杂多种格式,使用cout的成员函数来处理就显得很不方便了。STL另提供了一套
1
2
|
#include
#include
|
将输出内容按指定的宽度对齐,需要用到ios::right、ios::left、ios::internal和iomanip里的setw。其中setw用于指定要输出内容的对齐宽度。以下两段代码的结果完全相同,前面是一个浮点数-456.98,后面紧跟着一个字符串“The End”以及换行符“endl”。
代码一:
1
2
3
4
5
6
7
8
9
10
11
12
|
#include
#include
using
namespace
std;
int
main(
void
) {
cout.flags(ios::left);
//左对齐
cout << setw(10) << -456.98 <<
"The End"
<< endl;
cout.flags(ios::internal);
//两端对齐
cout << setw(10) << -456.98 <<
"The End"
<< endl;
cout.flags(ios::right);
//右对齐
cout << setw(10) << -456.98 <<
"The End"
<< endl;
return
0;
}
|
代码二:
1
2
3
4
5
6
7
8
9
|
#include
#include
using
namespace
std;
int
main(
void
) {
cout << left << setw(10) << -456.98 <<
"The End"
<< endl;
//左对齐
cout << internal << setw(10) << -456.98 <<
"The End"
<< endl;
//两端对齐
cout << right << setw(10) << -456.98 <<
"The End"
<< endl;
//右对齐
return
0;
}
|
结果:
-456.98 The End
- 456.98The End
-456.98The End
这里要额外说明的一点是,setw函数会用当前的填充字符控制对齐位置,默认的填充字符是空格。可以通过
1
2
3
4
5
6
7
|
#include
#include
using
namespace
std;
int
main(
void
) {
cout << setfill(
'0'
) << setw(10) << 45698 << endl;
return
0;
}
|
结果:
0000045698
输出整数的格式有按不同进制数出:ios::hex(16进制)、ios::dec(10进制)、ios::oct(8进制),也可强制其输出符号(正数也加上“+”号前缀),对于16进制的输出还可配合ios::uppercase使所有字母以大写表示。代码示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#include
#include
using
namespace
std;
int
main(
void
) {
cout.setf(ios::showpos | ios::uppercase);
cout << hex << setw(4) << 12 << setw(12) << -12 << endl;
cout << dec << setw(4) << 12 << setw(12) << -12 << endl;
cout << oct << setw(4) << 12 << setw(12) << -12 << endl;
cout.unsetf(ios::showpos | ios::uppercase);
cout << hex << setw(4) << 12 << setw(12) << -12 << endl;
cout << dec << setw(4) << 12 << setw(12) << -12 << endl;
cout << oct << setw(4) << 12 << setw(12) << -12 << endl;
return
0;
}
|
结果:
C FFFFFFF4
+12 -12
14 37777777764
c fffffff4
12 -12
14 37777777764
利用
1
2
3
4
5
6
7
8
|
#include
#include
using
namespace
std;
int
main(
void
) {
cout << showbase << setw(4) << hex << 32 << setw(4) << oct << 32 << endl;
cout << noshowbase << setw(4) << hex << 32 << setw(4) << oct << 32 << endl;
return
0;
}
|
结果:
0x20 040
20 40
上面代码中的showbase/noshobase也可以用cout的setf来代替,其结果是完全相同的:
1
2
3
4
5
6
7
8
9
10
|
#include
#include
using
namespace
std;
int
main(
void
) {
cout.setf(ios::showbase);
cout << setw(4) << hex << 32 << setw(4) << oct << 32 << endl;
cout.unsetf(ios::showbase);
cout << setw(4) << hex << 32 << setw(4) << oct << 32 << endl;
return
0;
}
|
小数可分为两种格式类型,一种是定点表示“ios::fixed”(不带指数域),另一种是科学计数法表示“ios::scientific”(带指数域)。与
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#include
#include
using
namespace
std;
int
main(
void
) {
cout.setf(ios::fixed);
cout << setprecision(0) << 12.05 << endl;
cout << setprecision(1) << 12.05 << endl;
cout << setprecision(2) << 12.05 << endl;
cout << setprecision(3) << 12.05 << endl;
cout.setf(ios::scientific, ios::floatfield);
cout << setprecision(0) << 12.05 << endl;
cout << setprecision(1) << 12.05 << endl;
cout << setprecision(2) << 12.05 << endl;
cout << setprecision(3) << 12.05 << endl;
return
0;
}
|
结果:
12
12.1
12.05
12.050
1.205000e+001
1.2e+001
1.21e+001
1.205e+001
需要注意的是,有时会因为机器的精度问题导致四舍五入的结果不正确。这种问题一般需要手动修正,见如下代码示例:
1
2
3
4
5
6
7
8
|
#include
#include
using
namespace
std;
int
main(
void
) {
cout << fixed << setprecision(1) << 2.05 << endl;
cout << fixed << setprecision(1) << 2.05 + 1e-8 << endl;
return
0;
}
|
结果:
2.0
2.1
字符串的输出处理主要是对齐,这一点在第二部分已经介绍过了,下面主要介绍字符串的输入方法。为了方便起见,我们使用
1
2
3
4
5
6
7
8
9
10
11
|
#include
#include
#include
using
namespace
std;
int
main(
void
) {
string str1, str2;
getline(cin, str1);
cin >> str2;
cout << str1 << endl << str2 << endl;
return
0;
}
|
输入:
abc
abc
结果:
abc
abc
由于调用系统函数在屏幕上逐个显示字符是很慢的,因此cin/cout为了加快速度使用缓冲区技术,粗略的讲就是暂时不输出指定的字符,而是存放在缓冲区中,在合适的时机一次性输出到屏幕上。如果单纯使用C++的输入/输出流来操作字符是不存在同步的问题的,但是如果要和C标准库的stdio库函数混合使用就必须要小心的处理缓冲区了。如果要与scanf和printf联合使用,务必在调用cout前加上cout.sync_with_stdio(),设置与stdio同步,否则输出的数据顺序会发生混乱。
flush和endl都会将当前缓冲区中的内容立即写入到屏幕上,而unitbuf/nounitbuf可以禁止或启用缓冲区。示例代码如下:
1
2
3
4
5
6
7
8
|
#include
#include
using
namespace
std;
int
main(
void
) {
cout << 123 << flush << 456 << endl;
cout << unitbuf << 123 << nounitbuf << 456 << endl;
return
0;
}
|
结果:
123456
123456
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#include
#include
#include
using
namespace
std;
struct
COMMODITY { string Name;
int
Id;
int
Cnt;
double
Price; };
int
main(
void
) {
COMMODITY cmd[] = {
{
"Fruit"
, 0x101, 50, 5.268},
{
"Juice"
, 0x102, 20, 8.729},
{
"Meat"
, 0x104, 30, 10.133},
};
cout << left << setw(8) <<
"NAME"
<< right << setw(8) <<
"ID"
;
cout << right << setw(8) <<
"COUNT"
<< right << setw(8) <<
"PRICE"
<< endl;
for
(
int
i = 0; i <
sizeof
(cmd) /
sizeof
(cmd[0]); ++i) {
cout << left << setw(8) << cmd[i].Name;
cout << right << hex << showbase << setw(8) << cmd[i].Id;
cout << dec << noshowbase << setw(8) << cmd[i].Cnt;
cout << fixed << setw(8) << setprecision(2) << cmd[i].Price << endl;
}
return
0;
}
|
结果:
NAME ID COUNT PRICE
Fruit 0x101 50 5.27
Juice 0x102 20 8.73
Meat 0x104 30 10.13