使用ostream和ios输出
  本节阅读量: 
  
  在本节中,我们将研究iostream输出类(ostream)的各个方面。
插入运算符
插入运算符(«)用于将信息放入输出流中。C++为所有内置数据类型预定义了插入操作,并且您已经看到了如何为自定义的类重载插入操作符。
在关于流的课程中,您看到istream和ostream都派生自一个名为ios的类。ios(和ios_base)的任务之一是控制输出的格式选项。
格式化
有两种方法可以更改格式选项:标志和操纵器。您可以将标志视为可以打开和关闭的布尔变量。操纵器是放置在流中的对象,影响事物的输入和输出方式。
要打开标志,请使用setf()函数,并将适当的标志作为参数。例如,默认情况下,C++不会在正数之前打印+号。然而,通过使用std::ios::showpos标志,我们可以更改此行为:
| 1
2
 | std::cout.setf(std::ios::showpos); // 打开 std::ios::showpos 标志
std::cout << 27 << '\n';
 | 
 
这将产生以下输出:
可以使用 OR(|)运算符一次打开多个ios标志:
| 1
2
 | std::cout.setf(std::ios::showpos | std::ios::uppercase); // 打开 std::ios::showpos 和 std::ios::uppercase 标志
std::cout << 1234567.89f << '\n';
 | 
 
输出:
要关闭标志,请使用unsetf()函数:
| 1
2
3
4
 | std::cout.setf(std::ios::showpos); // 打开 std::ios::showpos 标志
std::cout << 27 << '\n';
std::cout.unsetf(std::ios::showpos); // 关闭 std::ios::showpos 标志
std::cout << 28 << '\n';
 | 
 
这将产生以下输出:
在使用setf()时,还有一点需要注意的技巧。许多标志属于同一组,称为格式组。格式组是一组执行类似(有时互斥)格式选项的标志。例如,名为“basefield”的格式组包含标志“oct”(八进制)、“dec”(十进制)和“hex”(十六进制),这些标志控制整数值的基数。默认情况下,设置“dec”标志。因此,如果我们这样做:
| 1
2
 | std::cout.setf(std::ios::hex); // 按16进制输出数字
std::cout << 27 << '\n';
 | 
 
我们得到以下输出:
这不工作!原因是因为setf()仅打开标志——它不够聪明,无法关闭互斥标志。因此,当我们打开std::hex时,std::ios::dec仍处于打开状态,而std:∶ios::dec显然优先级更高。有两种方法可以解决这个问题。
首先,我们可以关闭std::ios::dec,以便仅设置std::hex:
| 1
2
3
 | std::cout.unsetf(std::ios::dec); // 关闭十进制格式输出
std::cout.setf(std::ios::hex); // 打开十六进制格式输出
std::cout << 27 << '\n';
 | 
 
现在我们得到了预期的输出:
第二种方法是使用不同形式的setf(),它接受两个参数:第一个参数是要设置的标志,第二个参数是它所属的格式化组。当使用这种形式的setf()时,属于该组的所有标志都被关闭,只有传入的标志被打开。例如:
| 1
2
3
 | // 设置 std::ios::hex 作为 std::ios::basefield 中的唯一标志
std::cout.setf(std::ios::hex, std::ios::basefield);
std::cout << 27 << '\n';
 | 
 
这也会产生预期的输出:
使用setf()和unsetf()往往会很尴尬,因此C++提供了第二种更改格式选项的方法:操纵器。操纵器的好处是它们足够聪明,可以打开和关闭适当的标志。下面是使用一些操纵器更改基础的示例:
| 1
2
3
 | std::cout << std::hex << 27 << '\n'; // 按十六进制打印 27
std::cout << 28 << '\n'; // 任然在16进制模式下
std::cout << std::dec << 29 << '\n'; // 切换回10进制
 | 
 
该程序生成输出:
通常,使用操纵器比设置和取消设置标志容易得多。许多选项通过标志和操纵器都可用,然而,有些选项仅通过标志或操纵器可用,因此了解如何同时使用这两个选项非常重要。
有用的格式化方式
下面是一些更有用的标志、操纵器和成员函数的列表。标志存在于std::ios类中,操纵器存在于std命名空间中,成员函数存在于std::ostream类中。
  
      
          | 标记 | 效果 | 
  
  
      
          | std::ios::boolalpha | 如果设置,则bool被打印为“true” 或 “false”,否则打印为 0 或 1 | 
  
  
      
          | 操纵器 | 效果 | 
  
  
      
          | std::boolalpha | bool被打印为“true” 或 “false” | 
      
          | std::noboolalpha | bool被打印为 0 或 1 | 
  
示例:
| 1
2
3
4
5
6
7
8
 | 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
2
3
4
 | 1 0
true false
1 0
true false
 | 
 
  
      
          | 标记 | 效果 | 
  
  
      
          | std::ios::showpos | 如果设置,正数前面会有一个 + 号 | 
  
  
      
          | 操纵器 | 效果 | 
  
  
      
          | std::showpos | 正数前面会有一个 + 号 | 
      
          | std::noshowpos | 正数前面不会带 + 号 | 
  
示例:
| 1
2
3
4
5
6
7
8
 | 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';
 | 
 
结果:
  
      
          | 标记 | 效果 | 
  
  
      
          | std::ios::uppercase | 如果设置,使用大写字母 | 
  
  
      
          | 操纵器 | 效果 | 
  
  
      
          | std::uppercase | 使用大写字母 | 
      
          | std::nouppercase | 使用小写字母 | 
  
示例:
| 1
2
3
4
5
6
7
8
 | 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
2
3
4
 | 1.23457e+007
1.23457E+007
1.23457e+007
1.23457E+007
 | 
 
  
      
          | 组 | 标记 | 效果 | 
  
  
      
          | std::ios::basefield | std::ios::dec | 按10进制打印数字(默认) | 
      
          | std::ios::basefield | std::ios::hex | 按16进制打印数字 | 
      
          | std::ios::basefield | std::ios::oct | 按8进制打印数字 | 
      
          | std::ios::basefield | (none) | 数字前导的格式打印 | 
  
  
      
          | 操纵器 | 效果 | 
  
  
      
          | std::dec | 按10进制打印数字 | 
      
          | std::hex | 按16进制打印数字 | 
      
          | std::oct | 按8进制打印数字 | 
  
示例:
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 | 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';
 | 
 
结果:
| 1
2
3
4
5
6
7
 | 27
27
33
1b
27
33
1b
 | 
 
现在,您应该能够看到通过标志和通过操纵器设置格式之间的关系。在未来的示例中,我们将使用操纵器,除非它们不可用。
精度、符号和小数点
使用操纵器(或标志),可以更改显示浮点数的精度和格式。有几个格式选项以某种复杂的方式组合在一起,因此我们将更仔细地看一看。
如果使用固定或科学记数法,则精度决定分数中显示的小数位数。请注意,如果精度小于有效位数,则数字将四舍五入。
  
      
          | 组 | 标记 | 效果 | 
  
  
      
          | std::ios::floatfield | std::ios::fixed | 按10进制打印浮点数 | 
      
          | std::ios::floatfield | std::ios::scientific | 按科学计数法打印浮点数 | 
      
          | std::ios::floatfield | (none) | 位数少,使用10进制打印,位数多,使用科学计数法 | 
      
          | std::ios::floatfield | std::ios::showpoint | 永远展示小数点 | 
  
  
      
          | 操纵器 | 效果 | 
  
  
      
          | std::fixed | 按10进制打印 | 
      
          | std::scientific | 按科学计数法打印 | 
      
          | std::showpoint | 永远展示小数点 | 
      
          | std::noshowpoint | 取消showpoint选项 | 
      
          | 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
 | 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';
 | 
 
产生结果:
|  1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 | 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数字,也不使用科学数字,则精度决定应显示多少有效数字。同样,如果精度小于有效位数,则该数字将四舍五入。
| 1
2
3
4
5
 | 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';
 | 
 
产生以下结果:
| 1
2
3
4
5
 | 123
123.5
123.46
123.456
123.456
 | 
 
使用showpoint操纵器或标志,可以使流写入小数点和尾随零。
| 1
2
3
4
5
6
 | 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';
 | 
 
产生以下结果:
| 1
2
3
4
5
 | 123.
123.5
123.46
123.456
123.4560
 | 
 
下面是一个包含更多示例的汇总表:
  
      
          | 选项 | 精度 | 12345.0 | 0.12345 | 
  
  
      
          | Normal | 3 | 1.23e+004 | 0.123 | 
      
          | Normal | 4 | 1.235e+004 | 0.1235 | 
      
          | Normal | 5 | 12345 | 0.12345 | 
      
          | Normal | 6 | 12345 | 0.12345 | 
      
          | Showpoint | 3 | 1.23e+004 | 0.123 | 
      
          | Showpoint | 4 | 1.235e+004 | 0.1235 | 
      
          | Showpoint | 5 | 12345. | 0.12345 | 
      
          | Showpoint | 6 | 12345.0 | 0.123450 | 
      
          | Fixed | 3 | 12345.000 | 0.123 | 
      
          | Fixed | 4 | 12345.0000 | 0.1235 | 
      
          | Fixed | 5 | 12345.00000 | 0.12345 | 
      
          | Fixed | 6 | 12345.000000 | 0.123450 | 
      
          | Scientific | 3 | 1.235e+004 | 1.235e-001 | 
      
          | Scientific | 4 | 1.2345e+004 | 1.2345e-001 | 
      
          | Scientific | 5 | 1.23450e+004 | 1.23450e-001 | 
      
          | Scientific | 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 | 符号左对齐,数字右对齐 | 
      
          | 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()操纵器来完成。请注意,默认设置为右对齐。
| 1
2
3
4
5
 | std::cout << -12345 << '\n'; // 未设置字段宽度
std::cout << std::setw(10) << -12345 << '\n'; // 设置字段宽度
std::cout << std::setw(10) << std::left << -12345 << '\n'; // 左对齐
std::cout << std::setw(10) << std::right << -12345 << '\n'; // 右对齐
std::cout << std::setw(10) << std::internal << -12345 << '\n'; // internally 对齐
 | 
 
这将产生以下结果:
| 1
2
3
4
5
 | -12345
    -12345
-12345
    -12345
-    12345
 | 
 
需要注意的一点是setw()和width()仅影响下一个输出语句。它们不像其他一些标志/操纵器那样是持久的。
现在,让我们设置填充字符并执行相同的示例:
| 1
2
3
4
5
6
 | std::cout.fill('*');
std::cout << -12345 << '\n'; // 未设置字段宽度
std::cout << std::setw(10) << -12345 << '\n'; // 设置字段宽度
std::cout << std::setw(10) << std::left << -12345 << '\n'; // 左对齐
std::cout << std::setw(10) << std::right << -12345 << '\n'; // 右对齐
std::cout << std::setw(10) << std::internal << -12345 << '\n'; // internally 对齐
 | 
 
这将产生输出:
| 1
2
3
4
5
 | -12345
****-12345
-12345****
****-12345
-****12345
 | 
 
请注意,字段中的所有空格都已用填充字符填充。
ostream类和iostream库包含其他可能有用的输出函数、标志和操纵器,具体取决于您需要执行的操作。与istream类一样,这些适合使用时查找对应的手册。
        
        
        