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::hex
, std::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 |
宽度,填充字符,和范围调整¶
... 未完
通常当你打印数字的时候,这些被打印的数字周围不会有任何的空间。然而,其实打印的数字可以向左或者向右对齐。为了完成这个,我们不得不定义一个字段宽度,定义了一个数字来规定输出空间的量。如果实际打印的数字小于字段宽度,它将左对齐或右对齐(如指定)。如果实际数字大于字段宽度,它将不会被截断——它将使字段溢出。