Skip to content

18.3 使用 ostrea 和 ios 进行输出

在这个部分,我们会看 ostream 的很多方面。

注意:本节课所有的 I/O 函数都在 std 命名空间中。那意味着,所有的 I/O 对象和函数都不得不使用前缀 std::,或者使用 using namespace std; ,这个情况必须向大家说明。

输入插入操作符

插入操作符 (<<) 被用来放置信息到一个输出流中。C++ 为所有的内建类型定义了插入操作,并且你早就学习过如何通过为你自己类进行操作符重载,来实现插入操作符。

在这节有关 stream 的课,你可以看到 istrea 和 ostream 都是派生自 ios 类。ios(和ios_base) 的工作之一就是控制输出的格式。

格式化

有两个方式来改变格式参数:flag 和 manipulators。你可以把 flags 看做布尔变量可以被开关。Manipulators 是放在 stream 影响输入和输出的事情。

要切换一个flag,使用 setf() 方法,可以合适的使用 flag 作为一个变量。例如,默认情况下,C++ 不会在正数前打印一个加号。然而,通过使用 std::ios::showpos 的 flag,我们可以改变这个表现:

std::cout.setf(std::ios::showpos); // turn on the std::ios::showpos flag
std::cout << 27 << '\n';

输出: +27

It is possible to turn on multiple ios flags at once using the OR (|) operator:

可以通过使用 OR (|) 操作符来在一次使用中开启多个操作符:

std::cout.setf(std::ios::showpos | std::ios::uppercase); // turn on the std::ios::showpos and std::ios::uppercase flag
std::cout << 27 << '\n';

要关闭 flag,使用 unsetf() 函数:

std::cout.setf(std::ios::showpos); // turn on the std::ios::showpos flag
std::cout << 27 << '\n';
std::cout.unsetf(std::ios::showpos); // turn off the std::ios::showpos flag
std::cout << 28 << '\n';

输出:

+27
28

当使用 setf() 时可以告诉大家一个其他的 bit 的使用技巧,许多 flags 属于一个组,叫做格式化组。一个格式化组是一组 flag 表现相似(有时候互相排斥)的格式化配置。例如,一个配置组叫做 “basefield” 包含几个flag ”oct“,”dec“,”hex“,可以控制整数的输出。默认情况下,”dec“ flag被设置,再这样的情况下,如果我们这样做:

std::cout.setf(std::ios::hex); // try to turn on hex output
std::cout << 27 << '\n';

会输出: 27

它不工作!这是因为 setf() 只会开启flag —— 它还不足够聪明到能关闭冲突的flags。在这种情况下,当我们开启了 std::ios::hexstd::ios::dec 也仍然是开启的。并且 std::ios::dec 显然优先。这里有两个方法来解决这个问题:

第一,我们可以关闭 std::ios::dec,以便于开启 std::ios::hex 生效:

std::cout.unsetf(std::ios::dec); // turn off decimal output
std::cout.setf(std::ios::hex); // turn on hexadecimal output
std::cout << 27 << '\n';

现在我们得到期望中的输出了:

1b

第二种方式就是使用一个不同形式的 setf() 会携带两个参数:第一个参数就是想要设置的 flag,然后第二个参数就是它属于的那个组。当使用这个形式的 setf() 时,所有的同组 flag 将会被关闭,然后只有 传入的 flag 会被打开:

// Turn on std::ios::hex as the only std::ios::basefield flag
std::cout.setf(std::ios::hex, std::ios::basefield);
std::cout << 27 << '\n';

这个也会如期输出:

1b

使用 setf()unsetf() 其实比较尴尬,因此 C++ 提供了一个第二的方式来改变格式化参数:manipulators。有关 manipulators 好的一方面是他们足够聪明来开启或者关闭和是的flags。这有一个使用 manipulators 改变 base 的例子:

std::cout << std::hex << 27 << '\n'; // print 27 in hex
std::cout << 28 << '\n'; // we're still in hex
std::cout << std::dec << 29 << '\n'; // back to decimal

输出

1b
1c
29

大体上讲,使用 manipulators 更容易比起设置和取消 flag 设置。许多配置都是可以通过 配置 flag 和 manipulators 来实现的。(例如改变 base),然而,其他的一些配置只允许通过 flags 或者 只允许通过 manipulators,因此了解如何使用他们二者非常重要。

有用的格式化

这是一个列表的一些最有用的 flags,manipulators,和一些成员函数。Flags 在 std::ios 类中,manipulators 在 std 命名空间中,然后成员函数在 std::ostream 类。

group flag meaning
std::ios::boolalpha If set, booleans print “true” or “false”. If not set, booleans print 0 or 1
Manipulator Meaning
std::boolalpha Booleans print “true” or “false”
std::noboolalpha Booleans print 0 or 1 (default)

例子:

std::cout << true << " " << false << '\n';

std::cout.setf(std::ios::boolalpha);
std::cout << true << " " << false << '\n';

std::cout << std::noboolalpha << true << " " << false << '\n';

std::cout << std::boolalpha << true << " " << false << '\n';

输出:

1 0
true false
1 0
true false
Group Flag Meaning
std::ios::showpos If set, prefix positive numbers with a +
Manipulator Meaning
std::showpos Prefixes positive numbers with a +
std::noshowpos Doesn’t prefix positive numbers with a +

例子:

std::cout << 5 << '\n';

std::cout.setf(std::ios::showpos);
std::cout << 5 << '\n';

std::cout << std::noshowpos << 5 << '\n';

std::cout << std::showpos << 5 << '\n';

输出:

5
+5
5
+5
Group Flag Meaning
std::ios::uppercase If set, uses upper case letters
Manipulator Meaning
std::uppercase Uses upper case letters
std::nouppercase Uses lower case letters
std::cout << 12345678.9 << '\n';

std::cout.setf(std::ios::uppercase);
std::cout << 12345678.9 << '\n';

std::cout << std::nouppercase << 12345678.9 << '\n';

std::cout << std::uppercase << 12345678.9 << '\n';

输出

1.23457e+007
1.23457E+007
1.23457e+007
1.23457E+007
Group Flag Meaning
std::ios::basefield std::ios::dec Prints values in decimal (default)
std::ios::basefield std::ios::hex Prints values in hexadecimal
std::ios::basefield std::ios::oct Prints values in octal
std::ios::basefield (none) Prints values according to leading characters of value
Manipulator Meaning
std::dec Prints values in decimal
std::hex Prints values in hexadecimal
std::oct Prints values in octal
std::cout << 27 << '\n';

std::cout.setf(std::ios::dec, std::ios::basefield);
std::cout << 27 << '\n';

std::cout.setf(std::ios::oct, std::ios::basefield);
std::cout << 27 << '\n';

std::cout.setf(std::ios::hex, std::ios::basefield);
std::cout << 27 << '\n';

std::cout << std::dec << 27 << '\n';
std::cout << std::oct << 27 << '\n';
std::cout << std::hex << 27 << '\n';

输出

27
27
33
1b
27
33
1b

到目前为止,你应该能够看出设置格式化通过 flag 和通过 manipulators 的区别了。在未来的例子,我们将使用 manipulators 除非不可用。

精度,符号,小数点

使用 manipulators (或者 flags),可以改变浮点数显示的精度和格式。有一些格式的选项以一种复杂的方式结合,我们接下来会仔细看

所在的组 Flag 意义
std::ios::floatfield std::ios::fixed 对浮点数采用十进制记法
std::ios::floatfield std::ios::scientific 对浮点数采用科学计数法
std::ios::floatfield (none) 如果位数很少则使用数字,否则使用科学计数法
std::ios::floatfield std::ios::showpoint 对于浮点数,始终展示小数点和后方的零
Manipulator Meaning
std::fixed 对数值使用十进制计数法
std::scientific 使用科学计数法
std::showpoint 显示浮点数的小数点和后方的零
std::noshowpoint 不要显示小数点和后方的零
std::setprecision(int) 设定浮点数的输出精度 (定义在 iomanip.h 头中)
Member function Meaning
std::precision() 返回当前设置的浮点数的精度
std::precision(int) 设置新的精度并且返回旧的精度

如果固定地或者科学计数法被使用,精度会决定分数中显示多少小数位。请注意,如果精度有效数字的个数,数字将会被四舍五入。

std::cout << std::fixed << '\n';
std::cout << std::setprecision(3) << 123.456 << '\n';
std::cout << std::setprecision(4) << 123.456 << '\n';
std::cout << std::setprecision(5) << 123.456 << '\n';
std::cout << std::setprecision(6) << 123.456 << '\n';
std::cout << std::setprecision(7) << 123.456 << '\n';

std::cout << std::scientific << '\n';
std::cout << std::setprecision(3) << 123.456 << '\n';
std::cout << std::setprecision(4) << 123.456 << '\n';
std::cout << std::setprecision(5) << 123.456 << '\n';
std::cout << std::setprecision(6) << 123.456 << '\n';
std::cout << std::setprecision(7) << 123.456 << '\n';

输出

123.456
123.4560
123.45600
123.456000
123.4560000

1.235e+002
1.2346e+002
1.23456e+002
1.234560e+002
1.2345600e+002

如果固定位数(fixed)和科学计数法(scientific)都没有被使用,精度会决定了该数字显示多少位。再说一次,如果精度小于有效数字个数,数字将会被四舍五入。

std::cout << std::setprecision(3) << 123.456 << '\n';
std::cout << std::setprecision(4) << 123.456 << '\n';
std::cout << std::setprecision(5) << 123.456 << '\n';
std::cout << std::setprecision(6) << 123.456 << '\n';
std::cout << std::setprecision(7) << 123.456 << '\n';

输出

123
123.5
123.46
123.456
123.456

使用了 showpoint manipulator 或者 flag,你可以让一个流写入数字和后面的零。

std::cout << std::showpoint << '\n';
std::cout << std::setprecision(3) << 123.456 << '\n';
std::cout << std::setprecision(4) << 123.456 << '\n';
std::cout << std::setprecision(5) << 123.456 << '\n';
std::cout << std::setprecision(6) << 123.456 << '\n';
std::cout << std::setprecision(7) << 123.456 << '\n';

输出

123.
123.5
123.46
123.456
123.4560

这是一些例子

Option Precision 12345.0 0.12345
Normal 3 1.23e+004 0.123
4 1.235e+004 0.1235
5 12345 0.12345
6 12345 0.12345
Showpoint 3 1.23e+004 0.123
4 1.235e+004 0.1235
5 12345. 0.12345
6 12345.0 0.123450
Fixed 3 12345.000 0.123
4 12345.0000 0.1235
5 12345.00000 0.12345
6 12345.000000 0.123450
Scientific 3 1.235e+004 1.235e-001
4 1.2345e+004 1.2345e-001
5 1.23450e+004 1.23450e-001
6 1.234500e+004 1.234500e-001

宽度,填充字符,和范围调整

... 未完

通常当你打印数字的时候,这些被打印的数字周围不会有任何的空间。然而,其实打印的数字可以向左或者向右对齐。为了完成这个,我们不得不定义一个字段宽度,定义了一个数字来规定输出空间的量。如果实际打印的数字小于字段宽度,它将左对齐或右对齐(如指定)。如果实际数字大于字段宽度,它将不会被截断——它将使字段溢出。