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';

这将产生以下输出结果:

img

可以使用按位或 (|) 运算符同时启用多个 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';

输出结果如下;

img

要关闭某个标志,请使用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';

这将产生以下输出结果:

img

使用 setf() 函数时还有一点需要注意。许多标志位属于不同的组,称为格式组。格式组是一组执行类似(有时互斥)格式化选项的标志位。例如,名为“basefield”的格式组包含标志位“oct”、“dec”和“hex”,用于控制整数值的进制。默认情况下,“dec”标志位会被设置。因此,如果我们执行以下操作:

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

我们得到以下输出:

img

它不起作用!原因在于 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';

img

第二种方法是使用另一种形式的 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';

这样也能产生预期的输出结果:

img

使用 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

该程序会生成以下输出:

img

一般来说,使用操纵符比设置和取消设置标志位要容易得多。许多选项可以通过标志位和操纵符实现(例如更改基址),但是,其他一些选项只能通过标志位或操纵符实现,因此了解如何使用这两种方法非常重要。

实用格式化程序

这里列出了一些比较常用的标志、操作符和成员函数。标志位于 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';

结果:

img

组(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';

结果:

img

组(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';

结果:

img

现在,你应该能够理解通过标志位和通过操纵符设置格式之间的关系了。在以后的示例中,除非操纵符不可用,否则我们将使用操纵符。

精度、表示法和小数点

使用操纵符(或标志),可以更改浮点数的显示精度和格式。有几种格式化选项,它们的组合方式较为复杂,因此我们将仔细研究一下。

组(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';

产生以下结果:

img

如果既不使用固定精度也不使用科学计数法,则精度决定了应显示的有效数字位数。同样,如果精度小于有效数字位数,则数字将被四舍五入。

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';

产生以下结果:

img

使用 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';

产生以下结果:

img

选项 精度 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

这将产生以下结果:

img

需要注意的是,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

这将产生以下输出:

img

请注意,字段中的所有空白处都已填充了填充字符。

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

posted @ 2025-12-07 09:45  游翔  阅读(9)  评论(0)    收藏  举报