23.3 - 使用ostream和ios处理输出
Key Takeaway
- 更改格式化的方式有两种,标记和manipulators。标记你可以将其看做是可以开关的布尔量。而manipulators则是放置在流中,可以修改输入输出的对象 。
setf()
和unsetf()
分别用于设置和关闭标记- 许多标志属于组,称为格式组。格式组是一组执行类似(有时互斥)格式化选项的标志,此时可以使用两个参数版本的
setf()
,并通过第二个参数指明组。此时组内其他标记会被自动关闭。
本节课我们将介绍iostream
的输出类ostream
。
插入运算符
插入运算符用于向输出流插入数据。C++为所有的内建数据结构都预定义了插入运算符,我们也可以通过重载输入输出运算符为自定义的类提供插入运算符。
在23.1 - 输入输出流中我们介绍过, istream
和 ostream
都是从 ios
类派生来的。ios
(和 ios_base
) 类的一个功能就是控制输出的格式。
格式化
更改格式化的方式有两种,标记和manipulators。标记你可以将其看做是可以开关的布尔量。而manipulators则是放置在流中,可以修改输入输出的对象 。
==设置一个标记可以使用 setf()
函数并提供合适的标记作为参数。==例如,默认情况下C++并不会在正数前加一个+号。但是,使用 std::ios::showpos
标记就可以修改该行为:
1 2 |
|
输出结果如下:
1 |
|
我们也可以利用按位或操作一次性打开多个 ios
标记 :
1 2 |
|
输出结果:
1 |
|
关闭标记也很简单,使用 unsetf()
函数即可:
1 2 3 4 |
|
输出结果如下:
1 2 |
|
在使用setf()
时还有一个需要提及的小技巧。许多标志属于组,称为格式组。格式组是一组执行类似(有时互斥)格式化选项的标志。例如,名为“basefield”的格式组包含标志“oct”、“dec”和“hex”,它们控制整数值的基数。默认情况下,设置了“dec”标志。因此,如果我们这样做:
1 2 |
|
则输出结果为:
1 |
|
没有效果!这是因为 setf()
只能打开标记——但是它没有智能到懂得去关闭互斥的标记。因此,当 std::ios::hex
开启时, std::ios::dec
仍然是开启状态,由于它优先级更高,所以仍然是按照十进制输出的。有两个办法可以解决这个问题。
第一种方法是关闭 std::ios::dec
并开启 std::hex
:
1 2 3 |
|
输出结果符合预期:
1 |
|
第二种方法是使用另一个版本的 setf()
,它有两个形参,第二个参数指明标记所属的格式组。当使用这个版本的 setf()
时,同组的其他标记都会被自动关闭,只有我们设置的标记会被打开。例如:
1 2 3 |
|
输出结果仍然符合预期:
1 |
|
使用 setf()
和 unsetf()
是有点别扭的,因此C++提供了第二种方法来改变格式化选项:manipulators。manipulators 最赞的地方在于它足够智能,它能够自动开关相关联的标记。下面的例子中使用了 manipulators 改变计数进制:
1 2 3 |
|
程序输出:
1 2 3 |
|
通常,使用manipulators比设置和取消设置标记要容易得多。许多标记都可以通过标志和manipulators来实现(例如更改基数),然而,另外一些标记则只能通过标记或manipulators实现,因此了解如何使用这两种方法非常重要。
有用的标记
这里我们列举一些很有用的标记、manipulators 以及成员函数。 标记位于 std::ios
类中,manipulators 则位于std命名空间总,而成员函数则位于 std::ostream
类中。
组标 | 记 | 含义 |
---|---|---|
std::ios::boolalpha |
打开该标记后,布尔类型会打印 “true” 或 “false”。如果关闭,则大0或1 |
Manipulator | 含义 |
---|---|
std::boolalpha |
布尔类型会打印 “true” or “false” |
std::noboolalpha |
布尔类型会打印 0 or 1 (default) |
例如:
1 2 3 4 5 6 7 8 |
|
结果:
1 2 3 4 |
|
组 | 标记 | 含义 |
---|---|---|
std::ios::showpos |
设置后,正数前面会添加+号 |
Manipulator | 含义 |
---|---|
std::showpos |
正数前面会添加+号 |
std::noshowpos |
正数前面不添加+号 |
例如:
1 2 3 4 5 6 7 8 |
|
结果:
1 2 3 4 |
|
组 | 标记 | 含义 |
---|---|---|
std::ios::uppercase |
If set, uses upper case letters |
Manipulator | 含义 |
---|---|
std::uppercase |
使用大写字母 |
std::nouppercase |
使用小写字母 |
例如:
1 2 3 4 5 6 7 8 |
|
结果:
1 2 3 4 |
|
组 | 标记 | 含义 |
---|---|---|
std::ios::basefield |
std::ios::dec |
按照十进制打印(默认的) |
std::ios::basefield |
std::ios::hex |
按照十六进制打印 |
std::ios::basefield |
std::ios::oct |
按照八进制打印 |
std::ios::basefield |
(none) | 根据前缀字母打印 |
Manipulator | Meaning |
---|---|
std::dec |
按照十进制打印 |
std::hex |
按照十六进制打印 |
std::oct |
按照八进制打印 |
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
结果:
1 2 3 4 5 6 7 |
|
到目前为止,你应该能够看到通过标志和通过操作符设置格式之间的关系。在以后的例子中,我们将使用操纵符,除非它们不可用。
精度、计数法和小数点
使用 manipulators (或者标记) 也可以改变数值打印时的精度和要展示的小数点位数。这些格式化选项的组合比较复杂,让我们仔细研究一下:
|组 |标记 |含义|
|:--|:--|:--|:--|
| |std::ios::floatfield
|std::ios::fixed
|对浮点数使用十进制记数法
| |std::ios::floatfield
|std::ios::scientific
|对浮点数使用科学记数法
| |std::ios::floatfield
|(none) |位数较少时使用固定计数法,位数多时使用科学计数法
| |std::ios::floatfield
|std::ios::showpoint
|对于浮点值,始终显示小数点和末尾0
Manipulator | 含义 |
---|---|
std::fixed |
使用十进制记数法 |
std::scientific |
使用科学计数法 |
std::showpoint |
对于浮点值,始终显示小数点和末尾0 |
std::noshowpoint |
对于浮点值,不显示小数点和末尾0 |
std::setprecision(int) |
为浮点数设置精度(定义在 iomanip 头文件中) |
成员函数 | 含义 |
---|---|
std::ios_base::precision() |
返回当前浮点数的精度 |
std::ios_base::precision(int) |
设置浮点数精度并返回之前的精度 |
如果使用固定记数法或科学记数法,则精度决定分数中显示的小数点后多少位。注意,如果精度小于有效位数,则数字将四舍五入。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
打印:
1 2 3 4 5 6 7 8 9 10 11 |
|
如果既不使用固定计数法也不使用科学计数法,则精度决定了应该显示多少有效数字。同样,如果精度小于有效位数,则数字将四舍五入。
1 2 3 4 5 |
|
打印结果:
1 2 3 4 5 |
|
使用 showpoint
manipulator 或标记,可以打印小数点和末尾0。
1 2 3 4 5 6 |
|
运行结果如下:
1 2 3 4 5 |
|
下面是一个有更多例子的汇总表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
宽度、填充字符和对齐
通常,当我们打印数字时是不考虑其周围的空间的。不过,我们的确可以让数字向左或向右对其。为此,我们必须首先定义一个字段宽度——即输出空间的大小。如果实际打印的数字小于字段宽度,它将被左对齐或右对齐(根据指定)。如果实际数字大于字段宽度,它并不会被截断——而是会溢出。
Group | Flag | Meaning |
---|---|---|
std::ios::adjustfield |
std::ios::internal |
Left-justifies the sign of the number, and right-justifies the value |
std::ios::adjustfield |
std::ios::left |
Left-justifies the sign and value |
std::ios::adjustfield |
std::ios::right |
Right-justifies the sign and value (default) |
Manipulator | Meaning |
---|---|
std::internal |
Left-justifies the sign of the number, and right-justifies the value |
std::left |
Left-justifies the sign and value |
std::right |
Right-justifies the sign and value |
std::setfill(char) |
Sets the parameter as the fill character (defined in the iomanip header) |
std::setw(int) |
Sets the field width for input and output to the parameter (defined in the iomanip header) |
Member function | Meaning |
---|---|
std::basic_ostream::fill() |
Returns the current fill character |
std::basic_ostream::fill(char) |
Sets the fill character and returns the old fill character |
std::ios_base::width() |
Returns the current field width |
std::ios_base::width(int) |
Sets the current field width and returns old field width |
为了使用这些格式化操作,我们必须先设置宽度。设置宽度可以通过 width(int)
成员函数来完成,或者通过 setw()
manipulator。注意,默认情况下是右对齐的。
1 2 3 4 5 |
|
运行结果为:
1 2 3 4 5 |
|
注意,setw()
和 width()
只对下一个输出语句有效,和其他的标记或manipulators不同,它们不是持久化的。
接下来,让我们设置一个填充字符并进行类似的操作:
1 2 3 4 5 6 |
|
输出结果:
1 2 3 4 5 |
|
注意,之前的空格被填充字符所替代了。
ostream
类和 iostream
库还提供了其他有用的输出函数、标记和 manipulators。和 istream
类一样,这些议题更适合放在专注于标准库的教程或书中(例如Nicolai M. Josuttis的“c++标准库”)。