28-3 使用 ostream 和 ios 进行输出
在本节中,我们将研究 iostream 输出类 (ostream) 的各个方面。
插入运算符
插入运算符 (<<) 用于将信息放入输出流中。C++ 为所有内置数据类型预定义了插入操作,您已经了解了如何为自己的类重载插入运算符。
在关于流的课程中,您已经看到 istream 和 ostream 都派生自一个名为 ios 的类。ios(以及 ios_base)的功能之一是控制输出的格式选项。
格式化
有两种方法可以更改格式选项:标志和操纵符。您可以将标志视为可以开启或关闭的布尔变量。操纵符是放置在流中的对象,它们会影响输入和输出的方式。
要启用某个标志,请使用setf() 函数,并将相应的标志作为参数传递。例如,默认情况下,C++ 不会在正数前面打印加号 (+)。但是,通过使用 std::ios::showpos 标志,我们可以改变这种行为:
std::cout.setf(std::ios::showpos); // turn on the std::ios::showpos flag
std::cout << 27 << '\n';
这将产生以下输出结果:

可以使用按位或 (|) 运算符同时启用多个 iOS 标志:
std::cout.setf(std::ios::showpos | std::ios::uppercase); // turn on the std::ios::showpos and std::ios::uppercase flag
std::cout << 1234567.89f << '\n';
输出结果如下;

要关闭某个标志,请使用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';
这将产生以下输出结果:

使用 setf() 函数时还有一点需要注意。许多标志位属于不同的组,称为格式组。格式组是一组执行类似(有时互斥)格式化选项的标志位。例如,名为“basefield”的格式组包含标志位“oct”、“dec”和“hex”,用于控制整数值的进制。默认情况下,“dec”标志位会被设置。因此,如果我们执行以下操作:
std::cout.setf(std::ios::hex); // try to turn on hex output
std::cout << 27 << '\n';
我们得到以下输出:

它不起作用!原因在于 setf() 函数只能启用标志位,它无法智能地禁用互斥的标志位。因此,当我们启用 std::hex 时,std::ios::dec 仍然处于启用状态,而 std::ios::dec 显然具有更高的优先级。有两种方法可以解决这个问题。
首先,我们可以关闭 std::ios::dec,这样就只设置 std::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';

第二种方法是使用另一种形式的 setf() 函数,它接受两个参数:第一个参数是要设置的标志,第二个参数是它所属的格式化组。使用这种形式的 setf() 函数时,该组中的所有标志都会被关闭,只有传入的标志会被打开。例如:
// 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';
这样也能产生预期的输出结果:

使用 setf() 和 unsetf() 函数通常比较繁琐,因此 C++ 提供了第二种方法来更改格式化选项:操纵符。操纵符的优点在于它们足够智能,能够自动启用和禁用相应的标志。以下是一个使用操纵符更改基本格式的示例:
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
该程序会生成以下输出:

一般来说,使用操纵符比设置和取消设置标志位要容易得多。许多选项可以通过标志位和操纵符实现(例如更改基址),但是,其他一些选项只能通过标志位或操纵符实现,因此了解如何使用这两种方法非常重要。
实用格式化程序
这里列出了一些比较常用的标志、操作符和成员函数。标志位于 std::ios 类中,操作符位于 std 命名空间中,成员函数位于 std::ostream 类中。
| 组(group) | 标志(Flag) | 意义(Meaning) |
|---|---|---|
| std::ios::boolalpha | 如果设置,则布尔值输出“true”或“false”。如果未设置,则布尔值输出0或1。 |
| 操作器(Manipulator) | 意义(Meaning) |
|---|---|
| std::boolalpha | 布尔值输出“true”或“false”。 |
| std::noboolalpha | 布尔值输出 0 或 1(默认值) |
例子:
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';
结果:

| 组(group) | 标志(Flag) | 意义(Meaning) |
|---|---|---|
| std::ios::uppercase | 如果设置,则使用大写字母 |
| 操作器(Manipulator) | 意义(Meaning) |
|---|---|
| std::uppercase | 使用大写字母 |
| std::nouppercase | 使用小写字母 |
例子:
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';
结果:

| 组(group) | 标志(Flag) | 意义(Meaning) |
|---|---|---|
| std::ios::basefield | std::ios::dec | 以十进制形式打印值(默认) |
| std::ios::basefield | std::ios::hex | 以十六进制形式打印值 |
| std::ios::basefield | std::ios::oct | 以八进制形式打印值 |
| std::ios::basefiled | (none) | 根据值的首字母打印值 |
| 操作器(Manipulator) | 意义(Meaning) |
|---|---|
| std::dec | 以十进制形式打印值 |
| std::hex | 以十六进制形式打印值 |
| std::oct | 以八进制形式打印值 |
例子:
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';
结果:

现在,你应该能够理解通过标志位和通过操纵符设置格式之间的关系了。在以后的示例中,除非操纵符不可用,否则我们将使用操纵符。
精度、表示法和小数点
使用操纵符(或标志),可以更改浮点数的显示精度和格式。有几种格式化选项,它们的组合方式较为复杂,因此我们将仔细研究一下。
| 组(group) | 标志(Flag) | 意义(Meaning) |
|---|---|---|
| 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 | 浮点数值不显示小数点和尾随的 0。 |
| std::setprecision(int) | 设置浮点数的精度(在 iomanip 头文件中定义) |
| 成员函数(Member function) | 意义(Meaning) |
|---|---|
| std::ios_base::precision() | 返回浮点数的当前精度 |
| std::ios_base::precision() | 设 |
如果使用固定记数法或科学记数法,精度决定分数中显示的小数位数。请注意,如果精度小于有效数字位数,则数字将被四舍五入。
#include <iomanip> //for std::setprecision
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';
产生以下结果:

如果既不使用固定精度也不使用科学计数法,则精度决定了应显示的有效数字位数。同样,如果精度小于有效数字位数,则数字将被四舍五入。
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';
产生以下结果:

使用 showpoint 操作符或标志,您可以使流写入小数点和尾随零。
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';
产生以下结果:

| 选项 | 精度 | 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 |
(std::showpoint) |
4 | 1.235e+004 |
0.1235 |
| 5 | 12345. |
0.12345 |
|
| 6 | 12345.0 |
0.123450 |
|
| 定点模式(Fiexd) | 3 | 12345.000 |
0.123 |
(std::fixed) |
4 | 12345.0000 |
0.1235 |
| 5 | 12345.00000 |
0.12345 |
|
| 6 | 12345.000000 |
0.123450 |
|
| 科学计数法(Scientific) | 3 | 1.235e+004 |
1.235e-001 |
(std::scientific) |
4 | 1.2345e+004 |
1.2345e-001 |
| 5 | 1.23450e+004 |
1.23450e-001 |
|
| 6 | 1.234500e+004 |
1.234500e-001 |
宽度、填充字符和对齐方式
通常情况下,打印数字时,数字本身不会考虑周围的空格。但是,我们可以对数字进行左对齐或右对齐。为此,我们首先需要定义一个字段宽度,它决定了每个数字输出时周围空格的数量。如果实际打印的数字小于字段宽度,则会根据指定的值进行左对齐或右对齐。如果实际打印的数字大于字段宽度,则不会进行截断,而是会溢出字段。
| 标志组 | 具体标志 | 意义 |
|---|---|---|
std::ios::adjustfield |
std::ios::internal |
数字符号左对齐,数值右对齐 |
std::ios::adjustfield |
std::ios::left |
符号和值左对齐 |
std::ios::adjustfield |
std::ios::right |
符号和值右对齐(默认) |
| 操作符 | 意义 | 头文件 |
|---|---|---|
std::internal |
数字符号左对齐,数值右对齐 | <ios> |
std::left |
符号和值左对齐 | <ios> |
std::right |
符号和值右对齐 | <ios> |
std::setfill(char) |
将参数设置为填充字符 | <iomanip> |
std::setw(int) |
设置输入和输出字段的宽度 | <iomanip> |
| 成员函数 | 意义 |
|---|---|
std::basic_ostream::fill() |
返回当前填充字符 |
std::basic_ostream::fill(char) |
设置填充字符并返回旧的填充字符 |
std::ios_base::width() |
返回当前字段宽度 |
std::ios_base::width(int) |
设置当前字段宽度并返回旧字段宽度 |
要使用这些格式化程序,首先需要设置字段宽度。这可以通过 width(int) 成员函数或 setw() 操作符来实现。请注意,默认情况下为右对齐。
std::cout << -12345 << '\n'; // print default value with no field width
std::cout << std::setw(10) << -12345 << '\n'; // print default with field width
std::cout << std::setw(10) << std::left << -12345 << '\n'; // print left justified
std::cout << std::setw(10) << std::right << -12345 << '\n'; // print right justified
std::cout << std::setw(10) << std::internal << -12345 << '\n'; // print internally justified
这将产生以下结果:

需要注意的是,setw() 和 width() 函数只会影响下一个输出语句,它们不像其他一些标志/操作符那样具有持久性。
现在,我们来设置一个填充字符,并重复上述示例:
std::cout.fill('*');
std::cout << -12345 << '\n'; // print default value with no field width
std::cout << std::setw(10) << -12345 << '\n'; // print default with field width
std::cout << std::setw(10) << std::left << -12345 << '\n'; // print left justified
std::cout << std::setw(10) << std::right << -12345 << '\n'; // print right justified
std::cout << std::setw(10) << std::internal << -12345 << '\n'; // print internally justified
这将产生以下输出:

请注意,字段中的所有空白处都已填充了填充字符。
ostream 类和 iostream 库包含其他输出函数、标志和操作符,根据您的具体需求,这些函数、标志和操作符可能很有用。与 istream 类一样,这些内容更适合在专注于标准库的教程或书籍中讲解。

浙公网安备 33010602011771号