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

这会产生以下输出

+27

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

这输出

+1.23457E+06

要关闭标志,请使用 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() 时还需要提及另一个小技巧。许多标志属于组,称为格式组。格式组是一组执行类似(有时互斥)格式化选项的标志。例如,名为“basefield”的格式组包含标志“oct”、“dec”和“hex”,它们控制整数值的基数。默认情况下,“dec”标志已设置。因此,如果我们这样做

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

我们得到以下输出

27

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

现在我们得到了预期的输出

1b

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

这也产生了预期的输出

1b

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

此程序产生输出

1b
1c
29

通常,使用操纵符比设置和取消设置标志容易得多。许多选项可以通过标志和操纵符(例如更改基数)获得,但是,其他选项只能通过标志或操纵符获得,因此了解如何使用两者非常重要。

有用的格式化器

以下是一些更有用的标志、操纵符和成员函数的列表。标志位于 std::ios 类中,操纵符位于 std 命名空间中,成员函数位于 std::ostream 类中。

标志含义
std::ios::boolalpha如果设置,布尔值打印“true”或“false”。如果未设置,布尔值打印 0 或 1

操纵符含义
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';

结果

1 0
true false
1 0
true false

标志含义
std::ios::showpos如果设置,正数前缀为 +

操纵符含义
std::showpos正数前缀为 +
std::noshowpos正数前不加 +

示例

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

标志含义
std::ios::uppercase如果设置,使用大写字母

操纵符含义
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';

结果

1.23457e+007
1.23457E+007
1.23457e+007
1.23457E+007

标志含义
std::ios::basefieldstd::ios::dec以十进制打印值(默认)
std::ios::basefieldstd::ios::hex以十六进制打印值
std::ios::basefieldstd::ios::oct以八进制打印值
std::ios::basefield(无)根据值的引导字符打印值

操纵符含义
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';

结果

27
27
33
1b
27
33
1b

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

精度、表示法和小数点

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

标志含义
std::ios::floatfieldstd::ios::fixed浮点数使用十进制表示法
std::ios::floatfieldstd::ios::scientific浮点数使用科学记数法
std::ios::floatfield(无)对于位数少的数字使用 fixed,否则使用 scientific
std::ios::floatfieldstd::ios::showpoint始终显示小数点和浮点值的尾随 0

操纵符含义
std::fixed值使用十进制表示法
std::scientific值使用科学记数法
std::showpoint显示小数点和浮点值的尾随 0
std::noshowpoint不显示小数点和浮点值的尾随 0
std::setprecision(int)设置浮点数的精度(定义在 iomanip 头文件中)

成员函数含义
std::ios_base::precision()返回浮点数的当前精度
std::ios_base::precision(int)设置浮点数的精度并返回旧精度

如果使用 fixed 或 scientific 表示法,精度决定了小数部分显示多少位小数。请注意,如果精度小于有效数字的数量,则数字将被四舍五入。

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 操纵符或标志,可以使流写入小数点和尾随零。

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

以下是一些更多示例的汇总表

选项精度12345.00.12345
正常31.23e+0040.123
41.235e+0040.1235
5123450.12345
6123450.12345
Showpoint31.23e+0040.123
41.235e+0040.1235
512345.0.12345
612345.00.123450
Fixed312345.0000.123
412345.00000.1235
512345.000000.12345
612345.0000000.123450
Scientific31.235e+0041.235e-001
41.2345e+0041.2345e-001
51.23450e+0041.23450e-001
61.234500e+0041.234500e-001

宽度、填充字符和对齐方式

通常,当您打印数字时,数字的打印不考虑其周围的空间。但是,可以将数字的打印左对齐或右对齐。为此,我们必须首先定义一个字段宽度,它定义一个值将具有的输出空间数量。如果实际打印的数字小于字段宽度,它将左对齐或右对齐(如指定)。如果实际数字大于字段宽度,它将不会被截断——它将溢出字段。

标志含义
std::ios::adjustfieldstd::ios::internal数字的符号左对齐,值右对齐
std::ios::adjustfieldstd::ios::left符号和值左对齐
std::ios::adjustfieldstd::ios::right符号和值右对齐(默认)

操纵符含义
std::internal数字的符号左对齐,值右对齐
std::left符号和值左对齐
std::right符号和值右对齐
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

这会产生结果

-12345
    -12345
-12345
    -12345
-    12345

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

这会产生输出

-12345
****-12345
-12345****
****-12345
-****12345

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

ostream 类和 iostream 库包含其他输出函数、标志和操纵符,它们可能很有用,具体取决于您需要做什么。与 istream 类一样,这些主题更适合专注于标准库的教程或书籍。

guest
您的电子邮箱地址将不会被显示
发现错误?请在上方留言!
与勘误相关的评论在处理后将被删除,以帮助减少混乱。感谢您帮助使网站对每个人都更好!
来自 https://gravatar.com/ 的头像与您提供的电子邮箱地址相关联。
有回复时通知我:  
92 评论
最新
最早 最多投票
内联反馈
查看所有评论