格式化输入与输出
除了条件状态外,每个iostream对象还维护一个格式状态来控制IO如何格式化的细节。
格式状态控制格式化的某些方面,如整型值是几进制、浮点值的精度、一个输出元素的宽度等。
标准库定义了一组操纵符来修改流的格式状态。
一个操纵符是一个函数或是一个对象,会影响流的状态,并能用作输入或输出运算符的运算对象。类似输入和输出运算符,操纵符也返回它所处理的流对象,因此我们可以在一条语句中组合操纵符和数据。
我们已经在程序中使用过一个操纵符——endl,我们将它“写”到输出流,就像它是一个值一样。但endl不是一个普通值,而是一个操作:它输出一个换行符并刷新缓冲区。
很多操纵符改变格式状态
操纵符用于两大类输出控制;控制数值的输出形式以及控制补白的数量和位置。
大多数改变格式状态的操纵符都是设置/复原成对的;
一个操纵符用来将格式状态设置为一个新值,而另一个用来将其复原,恢复为正常的默认格式。
当操纵符改变流的格式状态时,通常改变后的状态对所有后续IO都生效
当我们有一组IO操作希望使用相同的格式时,操纵符对格式状态的改变是持久的这特性很有用。
实际上,一些程序会利用操纵符的这一特性对其所有输入或输出重置一个成多个格式规则的行为。在这种情况下,操纵符会改变流这一特性就是满足要求的了。
但是,很多程序(而且更重要的是,很多程序员)期望流的状态符合标准库正常的默认设置。在这些情况下,将流的状态置于一个非标准状态可能会导致错误。因此,通常最好在不再需要特殊格式时尽快将流恢复到默认状态。
控制整型格式
控制布尔值的格式
操纵符改变对象的格式状态的一个例子是 boolalpha 操纵符。
默认情况下,bool值打印为1或0。一个true值输出为整数1,而false输出为0,我们可以通过对流使用booialpha操纵符来覆盖这种格式:
cout << "default bool values: " << true << " " << false;
cout<< "\nalpha bool values: " << boolalpha<< true << " " << false << endl;
执行这段程序会得到下面的结果:
default bool values: 1 0
alpha bool values: true false
一旦向cout“写入”了boolalpha,我们就改变了cout打印bool值的方式。后续打印bool值的操作都会打印true或false而非1或0。
为了取消cout格式状态的改变,我们使用noboolalpha:
cout << boolalpha //设置cout的内部状态
<< true
<< endl
<< noboolalpha//将内部状态恢复为默认格式
<< true << endl;
指定整型值的进制
默认情况下,整型值的输入输出使用十进制。
我们可以使用操纵符hex、oct和dec将其改为十六进制、八进制或是改回十进制:
cout << "default: " << 20 << " " << 1024 << endl;
cout << "octal: " << oct << 20 << " " << 1024 << endl;
cout << "hex: " << hex << 20 << " " << 1024 << endl;
cout << "decimal: " << dec << 20 << " " << 1024 << endl;
当编译并执行这段程序时,会得到如下输出:
注意,类似boolalpha,这些操纵符也会改变格式状态。它们会影响下一个和随后所有的整型输出,直至另一个操纵符又改变了格式为止。
操纵符hex、oct和dec只影响整型运算对象,浮点值的表示形式不受影响
在输出中指出进制
默认情况下,当我们打印出数值时,没有可见的线索指出使用的是几进制。
例如,20是十进制的20还是16的八进制表示?
当我们按十进制打印数值时,打印结果会符合我们的期望。
如果需要打印八进制值或十六进制值,应该使用showbase 操纵符。
当对流应用showbase 操纵符时,会在输出结果中显示进制,它遵循与整型常量中指定进制相同的规范:
- 前导0x表示十六进制。
- 前导0表示八进制。
- 无前导字符串表示十进制。
我们可以使用showbase修改前一个程序:
cout << showbase;// 当打印整型值时显示进制
cout << "default: " << 20 << " " << 1024 << endl;
cout << "in octal: " << oct << 20 << " " << 1024 << endl;
cout << "in hex: " << hex << 20 << " " << 1024 << endl;
cout << "indecimal: " << dec << 20 << " " << 1024 << endl;
cout << noshowbase; //恢复流状态
修改后的程序的输出会更清楚地表明底层值到底是什么:
操纵符noshowbase恢复cout的状态,从而不再显示整型值的进制。
默认情况下,十六进制值会以小写打印,前导字符也是小写的x。我们可以通过使用uppercase操纵符来输出大写的X并将十六进制数字a-f以大写输出:
cout << uppercase << showbase << hex
<< "printed in hexadecimal: " << 20 << " " << 1024
<<nouppercase << noshowbase << dec << endl;
这条语句生成如下输出:
printed in hexadecimal; 0X14 0X400
我们使用了操纵符nouppercase、noshowbase和dec来重置流的状态。
控制浮点数格式
我们可以控制浮点数输出三个种格式:
- 以多高精度(多少个数字)打印浮点值
- 数值是打印为十六进制,定点十进制还是科学记数法形式
- 对于没有小数部分的浮点值是否打印小数点
默认情况下,浮点值按六位数字精度打印;
cout << 3.1111111111 << endl;
如果浮点值没有小数部分,则不打印小数点。
cout << 3.0000000000 << endl;
根据浮点数的值选择打印成定点十进制或科学记数法形式。
标准库会选择一种可读性更好的格式:非常大和非常小的值打印为科学记数法形式,其他值打印为定点十进制形式。
指定打印精度
默认情况下,精度会控制打印的数字的总数。
当打印时,浮点值按当前精度舍入而非截断。
因此,如果当前精度为四位数字,则3.14159将打印为3.142,如果精度为三位数字,则打印为3,14。
我们可以通过调用IO对象的precision成员或使用setprecision操纵符来改变精度。
precision成员是重载的。
- 一个版本接受一个int值,将精度设置为此值,并返回旧精度值。
- 另一个版本不接受参数,返回当前精度值。
setprecision操纵符接受一个参数,用来设置精度。
操纵符 setprecision和其他接受参数的操纵符都定义在头文件 iomanip中。
下面的程序展示了控制浮点值打印精度的不同方法:
// cout.precision返回当前精度值
cout << "Precision: " << cout.precision()
<< ", Value:" << sqrt(2.0) << endl;
// cout.precision(12)将打印精度设置为12住数字
cout.precision(12);
cout << "Precision:" << cout.precision()
<< ", Value:" << sqrt (2.0) << endl;
// 另一种设置精度的方法是使用setprecision操纵符
cout << setprecision(3);
cout << "Precision:" << cout.precision()
<< ", Value:" << sqrt(2.0) << endl;
编译并执行这段程序,会得到如下输出:
此程序调用标准库sqrt函数,它定义在头文件cmath中。
sqrt函数是重载的,不同版本分别接受一个float、double或long double参数,返回实参的平方根。
注意:*表示默认流状态 | |
boolalpha | 将true和false输出为字符串 |
* noboolalpha | 将true和false输出为1,0 |
showbase | 对整型值输出表示进制的前缀 |
* noshowbase | 不生成表示进制的前缀 |
showpoint | 对浮点值总是显示小数点 |
*noshowpoint | 只有当浮点值包含小数部分时才显示小数点 |
showpos | 对非负数显示+ |
*noshowpos | 对非负数不显示+ |
uppercase | 在十六进制值中打印0X,在科学记数法中打印E |
*nouppercase | 在十六进制值中打印0x, 在科学记数法中打印e |
*dec | 整型值显示为十进制 |
hex | 整型值显示为十六进制 |
oct | 整型值显示为八进制 |
left | 在值的右侧添加填充字符 |
right | 在值的左侧添加填充字符 |
lnternal | 在符号和值之间添加填充字符 |
fixed | 浮点值显示为定点十进制 |
scientific | 浮点值显示为科学记数法 |
hexfloat | 浮点值显示为十六进制(C++11新特性) |
defaultfloat | 重置浮点数格式为十进制(C++11新特性) |
unitbuf | 每次输出操作后都刷新缓冲区 |
*nounitbuf | 恢复正常的缓冲区刷新方式 |
*skipws | 输入运算符跳过空白符 |
noskipws | 输入运算符不跳过空白符 |
flush | 刷新ostream缓冲区 |
ends | 插入空字符,然后刷新ostream缓冲区 |
endl | 插入换行,然后刷新ostream缓冲区 |
指定浮点数记数法
除非你需要控制浮点数的表示形式(如,按列打印数据或打印表示金额或百分比的数据),否则由标准库选择记数法是最好的方式。
通过使用恰当的操纵符,我们可以强制一个流使用科学记数法、定点十进制或是十六进制记数法。
- 操纵符scientific改变流的状态来使用科学记数法。
- 操纵符fixed改变流的状态来使用定点十进制。
- 在新标准库中,通过使用hexfloat 也可以强制浮点数使用十六进制格式。
- 新标准库还提供另一个名为defaultfloat的操纵符,它将流恢复到默认状态——根据要打印的值选择记数法。
这些操纵符也会改变流的精度的默认含义。
在执行 scientific、fixed或hexfloat后,精度值控制的是小数点后面的数字位数,而默认情况下精度值指定的是数字的总位数——既包括小数点之后的数字也包括小数点之前的数字。
使用fixed或scientific令我们可以按列打印数值,因为小数点距小数部分的距离是固定的:
cout << "default format: " << 100 * sqrt(2.0) << '\n'
<< "scientific: " << scientific << 100 * sqrt(2.0) << '\n'
<< "fixed decimal: " << fixed << 100 * sqrt(2.0) << '\n'
<< "hexadecimal: " << hexfloat << 100 * sqrt(2.0) << '\n'
<< "use defaults: " << defaultfloat << 100 * sqrt(2.0);
此程序会生成下面的输出:
默认情况下,十六进制数字和科学记数法中的e都打印成小写形式,我们可以用uppercase操纵符打印这些字母的大写形式。
打印小数点
强制打印小数点:
默认情况下,当一个浮点值的小数部分为0时,不显示小数点。showpoint 操纵符
cout << 10.0 << endl;// 打印10
cout << showpoint << 10.0 //打印10.0000
<< noshowpoint << endl; //恢复小数点的默认格式
操纵符noshowpoint恢复默认行为。下一个输出表达式将有默认行为,即,当浮点值的小数部分为0时不输出小数点。
输出补白
当按列打印数据时,我们常常需要非常精细地控制数据格式。
标准库提供了一些操纵符帮助我们完成所需的控制:
- setw指定下一个数字或字符串值的最小空间。
- left表示左对齐输出。
- right表示右对齐输出,右对齐是默认格式。
- internal控制负数的符号的位置,它左对齐符号,右对齐值,用空格填满所有中间空间。
- setfill允许指定一个字符代替默认的空格来补白输出。
setw类似endl,不改变输出流的内部状态。它只决定下一个输出的大小。
下面程序展示了如何使用这些操纵符:
int i = -16;
double d = 3.14159;
//补白第一列,使用输出中最小12个位置
cout << "i:" << setw(12) << i << "next col" << '\n'
<< "d: " << setw(12) << d << "next col" << '\n';
//补白第一列,左对齐所有列
cout << left
<< "i: " << setw(12) << i << "next col" << '\n'
<< "d: " << setw(12) << d << "next col" << '\n'
<< right; //恢复正常对齐
//补白第一列,右对齐所有列
cout << right
<< "i: " << setw(12) << i << "next col" << '\n'
<< "d: " << setw(12) << d << "next col" << '\n';
//补白第一列,但补在域的内部
cout << internal
<< "i:" << setw(12)<< i << "next col" << '\n'
<< "d: " << setw(12)<< d << "next col" << '\n';
//补白第一列,用#作为补白字符 符。
cout << setfill('#')
<< "i: " << setw(12) << 1 << "next col" << '\n'
<< "d: " << setw(12) << d << "next col" << '\n'
<< setfill(' ');//恢复正常的补白字符
执行这段程序,会得到下面的输出:
setfill(ch) | 用ch填充空白 |
setprecision(n) | 将浮点精度设置为n |
setw(w) | 读或写值的宽度为w个字符 |
setbase(b) | 将整数输出为b进制 |
控制输入格式
默认情况下,输入运算符会忽略空白符(空格符、制表符、换行符、换纸符和回车符)。
下面的循环
char ch;
while (cin >> ch)
cout << ch;
当给定下面输入序列时
a b c
d
循环会执行4次,读取字符a到d,跳过中间的空格以及可能的制表符和换行符。
此程序的输出是
abcd
操纵符 noskipws会令输入运算符读取空白符,而不是跳过它们。为了恢复默认行为,我们可以使用skipws操纵符:
cin >> noskipws;// 设置cin 读取空白符
while (cin >> ch)
cout << ch;
cin >> skipws; //将cin恢复到默认状态,从而丢弃空白符
给定与前一个程序相同的输入,此循环会执行7次,从输入中既读取普通字符又读取空白符。此循环的输出为
a b c
d