第06讲:格式化输入输出

《计算机程序设计》

苏醒

计算机学院计算机研究所编译系统研究室

课前准备

  • 在WSL Ubuntu中建立一个专门的目录用于课上练习,如~/course
$ mkdir ~/course
  • 打开VSCode并连接到WSL,打开此目录并新建文件,如~/course/main.cpp
  • 编辑main.cpp,随堂编程练习的代码请直接在此文件中编辑
main.cpp
#include <iostream>
#include <iomanip>
using namespace std;

int main() {
  // ============= begin =============

  // =============  end  =============
  return 0;
}

温故

测一测

南北朝《张丘建算经》中叙述了一个”女不善织“问题,本质上是等差级数求和。请补充代码,使用循环求解该问题。

有女不善织,日减功,迟。初日织五尺,末日织一尺,今三十日织讫,问织几何?

#include <iostream>
using namespace std;

int main() {
  double sum = 0;
  double decrease = (5 - 1) / 29.0;
  // ============= begin =============

  // =============  end  =============
  cout << sum << endl;
  return 0;
}

输出ASCII码表

编程求解

编写程序,输出下面的ASCII码表:

  • 表格展示编码32–159范围内的字符,其中32–126为可见字符,不可见字符无需输出
  • 每行输出8个(Hex, Char)对,其中Hex为字符十六进制编码,Char为字符本身
  • Hex列宽度为4,Char列宽度为5,表头左对齐, 字符编码与字符居中对齐
+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+
|Hex |Char |Hex |Char |Hex |Char |Hex |Char |Hex |Char |Hex |Char |Hex |Char |Hex |Char |
+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+
| 20 |     | 30 |  0  | 40 |  @  | 50 |  P  | 60 |  `  | 70 |  p  | 80 |     | 90 |     |
| 21 |  !  | 31 |  1  | 41 |  A  | 51 |  Q  | 61 |  a  | 71 |  q  | 81 |     | 91 |     |
| 22 |  "  | 32 |  2  | 42 |  B  | 52 |  R  | 62 |  b  | 72 |  r  | 82 |     | 92 |     |
| 23 |  #  | 33 |  3  | 43 |  C  | 53 |  S  | 63 |  c  | 73 |  s  | 83 |     | 93 |     |
| 24 |  $  | 34 |  4  | 44 |  D  | 54 |  T  | 64 |  d  | 74 |  t  | 84 |     | 94 |     |
| 25 |  %  | 35 |  5  | 45 |  E  | 55 |  U  | 65 |  e  | 75 |  u  | 85 |     | 95 |     |
| 26 |  &  | 36 |  6  | 46 |  F  | 56 |  V  | 66 |  f  | 76 |  v  | 86 |     | 96 |     |
| 27 |  '  | 37 |  7  | 47 |  G  | 57 |  W  | 67 |  g  | 77 |  w  | 87 |     | 97 |     |
| 28 |  (  | 38 |  8  | 48 |  H  | 58 |  X  | 68 |  h  | 78 |  x  | 88 |     | 98 |     |
| 29 |  )  | 39 |  9  | 49 |  J  | 59 |  Z  | 69 |  j  | 79 |  z  | 89 |     | 99 |     |
| 2A |  *  | 3A |  :  | 4A |  K  | 5A |  Z  | 6A |  k  | 7A |  z  | 8A |     | 9A |     |
| 2B |  +  | 3B |  ;  | 4B |  L  | 5B |  [  | 6B |  l  | 7B |  {  | 8B |     | 9B |     |
| 2C |  ,  | 3C |  <  | 4C |  M  | 5C |  \  | 6C |  m  | 7C |  |  | 8C |     | 9C |     |
| 2D |  -  | 3D |  =  | 4D |  N  | 5D |  ]  | 6D |  n  | 7D |  }  | 8D |     | 9D |     |
| 2E |  .  | 3E |  >  | 4E |  O  | 5E |  ^  | 6E |  o  | 7E |  ~  | 8E |     | 9E |     |
| 2F |  /  | 3F |  ?  | 4F |  P  | 5F |  _  | 6F |  p  | 7F |     | 8F |     | 9F |     |
+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+

学习目标

  • 学习内容
    • 格式化标志与方法
    • IO操纵子
    • 输入输出错误处理

学习目标

  • 理解流式输入输出的内涵
  • 掌握格式化输入输出的两种方法,尤其是IO操纵子的使用方法
  • 检测并处理输入输出错误

运用本次课所学内容,编程输出ASCII码表

格式化输入输出

流式输入输出(回顾)

  • 流(stream)是C++对于输入输出设备的抽象
    • 流的核心内涵是一个数据的线性序列,数据从流中流入或流出
    • 流的方向分为输入流和输出流
#include <iostream>
using namespace std;

int main() {
  int n;
  cin >> n;                        // 输入流
  cout << "Hello, World!" << endl; // 输出流
  return 0;
}
  • C++标准库提供了iostream库,定义了istreamostream
    • cincoutistreamostream类的对象,分别表示标准输入流标准输出流
    • 流对象使用流插入运算符<<和流提取运算符>>进行输出/输入操作

格式化输入

  • 输入流对象提供流提取运算符>>,用于从输入流中进行格式化输入
    • 格式化输入指采用适配数据类型的方式从流中提取数据,即包含数据解析过程
    • >>会自动跳过空白字符(空格、制表符、换行符等),并尽可能多地提取数据
    • >>支持各种基础数据类型的输入,包括布尔型、整型、浮点型、字符型等
格式化输入
  bool b; int n; double d; char c;
  cout << "Enter a boolean, an integer, a double, and a character: ";
  cin >> b >> n >> d >> c;
  cout << "You entered: " << b << ", " << n << ", " << d << ", " << c << endl;
Enter a boolean, an integer, a double, and a character: 0 33 2.6 ^
You entered: 0, 33, 2.6, ^

格式化输出

  • 输出流对象提供流插入运算符<<,用于向输出流进行格式化输入
    • 格式化输入指采用适配数据类型的方式向流中插入数据
    • <<支持各种基础数据类型的输出,包括布尔型、整型、浮点型、字符型等
格式化输出
  bool b = false;
  int i = 33;
  double d = 2.6;
  char c = '^';
  cout << "Output: " << b << ", " << i << ", " << d << ", " << c << endl;
Output: 0, 33, 2.6, ^

思考

如何实现更加精细的格式控制?例如

  • 整数的输入/输出格式:十进制、十六进制、八进制
  • 浮点数的输入/输出格式:科学计数法、精度控制

格式化标志与方法

格式化标志

  • 流对象提供了一组格式化标志,用于控制输入/输出格式
C++流对象的格式化标志
标志 描述 标志 描述
dec 十进制表示 scientific 使用科学计数法表示浮点数
oct 八进制表示 fixed 使用固定小数点表示浮点数
hex 十六进制表示 boolalpha 将布尔值输出为truefalse
left 左对齐输出 showbase 显示整数的进制前缀
right 右对齐输出 showpoint 显示浮点数的小数点
internal 符号和数值分别左右对齐 showpos 显示正数符号
uppercase 使用大写字母表示十六进制前缀 skipws 跳过空白字符

格式化标志管理

  • 流对象提供了方法用于读取、设置格式化标志
    • flags():读取设置格式化标志
    • setf()/unsetf():设置/清除指定格式化标志
格式化标志位
#include <iostream>
using namespace std;
int main() {
  cout.setf(ios::showbase);
  cout << "Default flags: " << hex << cout.flags() << endl;
  cout.setf(ios::showpos);
  cout << "Show pos: " << hex << cout.flags() << endl;
  cout.unsetf(ios::showpos);
  cout << "Unset show pos: " << hex << cout.flags() << endl;
  return 0;
}
Default flags: 0x1208
Show pos: 0x1a08
Unset show pos: 0x1208
格式化标志位
bit 标志位 bit 标志位
0 boolalpha 8 scientific
1 dec 9 showbase
2 fixed 10 showpoint
3 hex 11 showpos
4 internal 12 skipws
5 left 13
6 oct 14 uppercase
7 right 15

格式化标志组

  • 某些功能相关的格式化标志可构成格式化标志组
    • basefield:进制标志组,包括decocthex
    • floatfield:浮点数格式标志组,包括scientificfixed
    • adjustfield:对齐标志组,包括leftrightinternal
  • setf()方法支持设置格式化标志时清除同组的其他标志
    • 例如,setf(ios::hex, ios::basefield)会清除decoct标志

考考你

这样的设计是处于何种考虑?

输入时不忽略空白字符

  • 默认情况下,格式化输入会自动跳过空白字符
  • 清除skipws标志位后,输入流会将空白字符作为有效字符读取
不忽略空白字符
  char c0, c1;
  cout << "Enter two characters (skipws): ";
  cin >> c0 >> c1;
  cout << "You entered: " << c0 << ", " << c1 << endl;
  cout << "Enter two characters (noskipws): ";
  cin.unsetf(ios::skipws);
  cin >> c0 >> c1;
  cout << "You entered: " << c0 << ", " << c1 << endl;
Enter two characters (skipws): a b
You entered: a, b
Enter two characters (noskipws): a b
You entered: 
, a

考考你

设置noskipws标志位时的输出是如何产生的?

设置布尔数输入输出格式

  • 默认情况下,布尔值的输入与输出只能为01
  • 通过设置boolalpha标志,布尔值的输入与输出可以为truefalse
设置布尔数输入输出
  bool b0 = true, b1 = false, b;
  cout << "Output (1/0): " << b0 << ", " << b1 << endl;
  cout.setf(ios::boolalpha);
  cout << "Output (true/false): " << b0 << ", " << b1 << endl;
  cout << "Enter a boolean (1/0): ";
  cin >> b;
  cout << "You entered: " << b << endl;
  cout << "Enter a boolean (true/false): ";
  cin.setf(ios::boolalpha);
  cin >> b;
  cout << "You entered: " << b << endl;
Output (1/0): 1, 0
Output (true/false): true, false
Enter a boolean (1/0): 1
You entered: true
Enter a boolean (true/false): false
You entered: false
Output (1/0): 1, 0
Output (true/false): true, false
Enter a boolean (1/0): true
You entered: false
Enter a boolean (true/false): You entered: false
Output (1/0): 1, 0
Output (true/false): true, false
Enter a boolean (1/0): 0
You entered: false
Enter a boolean (true/false): 1
You entered: false

设置输入数字进制

设置输入数字进制
  int n;
  // 设置读取十六进制数,带前缀(0x/0X)、不带前缀均可
  cout << "Enter a number (hex): ";
  cin.setf(ios::hex, ios::basefield);
  cin >> n;
  cout << "You entered: " << n << endl;
  // 设置读取八进制数,带前缀(0)、不带前缀均可
  cout << "Enter a number (oct): ";
  cin.setf(ios::oct, ios::basefield);
  cin >> n;
  cout << "You entered: " << n << endl;
  // 设置读取十进制数
  cout << "Enter a number (dec): ";
  cin.setf(ios::dec, ios::basefield);
  cin >> n;
  cout << "You entered: " << n << endl;
Enter a number (hex): 10
You entered: 16
Enter a number (oct): 10
You entered: 8
Enter a number (dec): 10
You entered: 10
Enter a number (hex): 0x10
You entered: 16
Enter a number (oct): 010
You entered: 8
Enter a number (dec): 10
You entered: 10

设置输出数字进制

设置输出数字进制
  int n = 123;
  cout.setf(ios::dec, ios::basefield);
  cout << "Output (dec): " << n << endl;
  cout.setf(ios::hex, ios::basefield);
  cout << "Output (hex): " << n << endl;
  cout.setf(ios::oct, ios::basefield);
  cout << "Output (oct): " << n << endl;
  cout.setf(ios::showbase);
  cout.setf(ios::oct, ios::basefield);
  cout << "Output (oct+showbase): " << n << endl;
  cout.setf(ios::hex, ios::basefield);
  cout << "Output (hex+showbase): " << n << endl;
  cout.setf(ios::uppercase);
  cout << "Output (hex+showbase+uppercase): " << n << endl;
Output (dec): 123
Output (hex): 7b
Output (oct): 173
Output (oct+showbase): 0173
Output (hex+showbase): 0x7b
Output (hex+showbase+uppercase): 0X7B

设置浮点数输出格式

  • 默认情况下,浮点数输出格式与数值大小有关,过大/过小的数使用科学计数法
  • 通过设置scientificfixed标志,可以选择浮点数的输出格式
设置浮点数输出格式
  double f = 123.456789, small = 1.23456789e-10, big = 1.23456789e10;
  cout << "Output (default): " << f << ", " << small << ", " << big << endl;
  cout.setf(ios::fixed, ios::floatfield);
  cout << "Output (fixed): " << f << ", " << small << ", " << big << endl;
  cout.setf(ios::scientific, ios::floatfield);
  cout << "Output (scientific): " << f << ", " << small << ", " << big << endl;
  cout.setf(ios::fixed);
  cout << "Output (fixed+scientifc): " << f << ", " << small << ", " << big << endl;
  cout.setf(ios::scientific, ios::floatfield); cout.setf(ios::uppercase);
  cout << "Output (scientific+uppercase): " << f << ", " << small << ", " << big << endl;
  cout.setf(ios::fixed, ios::floatfield); cout.setf(ios::uppercase);
  cout << "Output (fixed+uppercase): " << f << ", " << small << ", " << big << endl;
Output (default): 123.457, 1.23457e-10, 1.23457e+10
Output (fixed): 123.456789, 0.000000, 12345678900.000000
Output (scientific): 1.234568e+02, 1.234568e-10, 1.234568e+10
Output (fixed+scientifc): 0x1.edd3c07ee0b0bp+6, 0x1.0f7bfe5db09ebp-33, 0x1.6fee0e1ap+33
Output (scientific+uppercase): 1.234568E+02, 1.234568E-10, 1.234568E+10
Output (fixed+uppercase): 123.456789, 0.000000, 12345678900.000000

设置浮点数输出精度

  • 默认情况下,浮点数输出精度规则为
    • 不设置浮点格式标志,显示6位有效数字
    • 设置fixed/scientific标志,显示小数点后6位
  • 通过设置precision()方法,可以控制浮点数输出精度
设置浮点数输出精度
  double f = 123.456789, small = 1.23456789e-10, big = 1.23456789e10;
  cout << "Output (default): " << f << ", " << small << ", " << big << endl;
  cout.precision(2);
  cout << "Output (precision=2): " << f << ", " << small << ", " << big << endl;
  cout.setf(ios::scientific, ios::floatfield);
  cout << "Output (precision=2+scientific): " << f << ", " << small << ", " << big << endl;
  cout.setf(ios::fixed, ios::floatfield);
  cout << "Output (precision=2+fixed): " << f << ", " << small << ", " << big << endl;
Output (default): 123.457, 1.23457e-10, 1.23457e+10
Output (precision=2): 1.2e+02, 1.2e-10, 1.2e+10
Output (precision=2+scientific): 1.23e+02, 1.23e-10, 1.23e+10
Output (precision=2+fixed): 123.46, 0.00, 12345678900.00

设置小数点与正负号

  • 默认情况下,正数的输出不显示正号,小数部分为0时不显示小数点
  • 通过设置showpointshowpos标志,可以选择显示小数点、正数符号
设置小数点与正负号
  double f = 123.00;
  cout << "Output (default): " << f << endl;
  cout.setf(ios::showpoint);
  cout << "Output (showpoint): " << f << endl;
  cout.setf(ios::showpos);
  cout << "Output (showpos+showpoint): " << f << endl;
  cout.unsetf(ios::showpoint);
  cout << "Output (showpos): " << f << endl;
Output (default): 123
Output (showpoint): 123.000
Output (showpos+showpoint): +123.000
Output (showpos): +123

设置输出域宽与填充

  • 通过width()方法设置输出域宽,若输出内容不足域宽则以填充字符补足
  • 通过fill()方法可改变填充字符
设置输出域宽与填充
  double n = -1.23;
  cout << "(default): " << n << endl;
  cout << "(width=8): ";
  cout.width(8);
  cout << n << endl;
  cout << "(width=8, fill='*'): ";
  cout.fill('*');
  cout.width(8);
  cout << n << endl;
(default): -1.23
(width=8):    -1.23
(width=8, fill='*'): ***-1.23

明察秋毫

  • width()方法仅对下一个输出操作有效,之后的输出操作不受影响
  • fill()方法效果是持久的,直到下次调用fill()方法改变填充字符

设置输出对齐

  • 通过leftrightinternal标志设置对齐方式
设置输出对齐
  double n = -1.23;
  cout.fill('*');
  cout << "(default): ";
  cout.width(8);
  cout << n << endl;
  cout << "(width=8, fill='*', left): ";
  cout.setf(ios::left, ios::adjustfield);
  cout.width(8);
  cout << n << endl;
  cout << "(width=8, fill='*', internal): ";
  cout.setf(ios::internal, ios::adjustfield);
  cout.width(8);
  cout << n << endl;
  cout << "(width=8, fill='*', right): ";
  cout.setf(ios::right, ios::adjustfield);
  cout.width(8);
  cout << n << endl;
(default): ***-1.23
(width=8, fill='*', left): -1.23***
(width=8, fill='*', internal): -***1.23
(width=8, fill='*', right): ***-1.23

格式化输出练习

考考你

如何输出ASCII码表的表头?

Hex列宽度为4,Char列宽度为5,表头左对齐, 字符编码与字符居中对齐

+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+
|Hex |Char |Hex |Char |Hex |Char |Hex |Char |Hex |Char |Hex |Char |Hex |Char |Hex |Char |
+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+----+-----+
  • 通过设置域宽与填充字符输出----+-----+
  • 通过设置域宽与对齐方式输出HexChar (含右侧空格)

IO操纵子

IO操纵子

  • C++标准库提供了IO操纵子manipulator)用于控制格式化输入输出
    • IO操纵子通过流插入运算符<<插入到流对象中,从而改变流对象的状态
状态控制设置对齐
  double n = -1.23;
  cout.fill('*');
  cout << "(default): ";
  cout.width(8);
  cout << n << endl;
  cout << "(width=8, fill='*', left): ";
  cout.setf(ios::left, ios::adjustfield);
  cout.width(8);
  cout << n << endl;
  cout << "(width=8, fill='*', internal): ";
  cout.setf(ios::internal, ios::adjustfield);
  cout.width(8);
  cout << n << endl;
  cout << "(width=8, fill='*', right): ";
  cout.setf(ios::right, ios::adjustfield);
  cout.width(8);
  cout << n << endl;
IO操纵子设置对齐
  double n = -1.23;
  cout << setfill('*') << "(default): " << setw(8) << n << endl;
  cout << "(width=8, fill='*', left): " << left << setw(8) << n << endl;
  cout << "(width=8, fill='*', internal): " << internal << setw(8) << n << endl;
  cout << "(width=8, fill='*', right): " << right << setw(8) << n << endl;

温馨提示

  • 使用IO操纵子请包含头文件<iomanip>
  • IO操纵子可以简化格式控制代码,提高代码可读性

IO操纵子列表

IO操纵子与相关的流状态/方法
操纵子 相关标志位/方法
dec, oct, hex dec, oct, hex
scientific, fixed, hexfloat, defaultfloat scientific, fixed
left, right, internal left, right, internal
boolalpha, noboolalpha boolalpha
showbase, noshowbase showbase
showpoint, noshowpoint showpoint
showpos, nonshowpos showpos
skipws, noskipws skipws
uppercase, nouppercase uppercase
setw() width()
setfill() fill()
setprecision() precision()

输入输出错误处理

输入错误

格式化输入
  bool b; int n; double d; char c;
  cout << "Enter a boolean, an integer, a double, and a character: ";
  cin >> b >> n >> d >> c;
  cout << "You entered: " << b << ", " << n << ", " << d << ", " << c << endl;
Enter a boolean, an integer, a double, and a character: 0 33 2.6 ^
You entered: 0, 33, 2.6, ^

Try it!

运行上述程序,尝试输入”3 1 2 d”,观察输出结果

输入错误检测

  • >>无法将输入流中的数据转换为目标数据类型时,会导致输入流状态错误
    • 当处于输入流状态错误时,后续输入操作将不会执行
    • 输入流对象提供了fail()方法用于检测输入流状态是否错误
    • 输入流对象提供了clear()方法用于清除输入流状态错误
errorinput.cpp
  bool b; int n;
  cout << "Enter a boolean, an integer: ";
  cin >> b;
  if (cin.fail()) {
    cout << "Invalid input for boolean" << endl; // 若检测到错误则报告错误信息
    cin.clear();                                 // 清除fail标志位以便继续输入
  }
  cin >> n;
  cout << "You entered: " << b << ", " << n << endl;
Enter a boolean, an integer: 3 3.4
Invalid input for boolean
You entered: 1, 3

流的状态

  • 流对象内部维护若干个状态标志,用于记录输入/输出流的状态
    • goodbit:流状态正常
    • eofbit:流到达文件尾
    • failbit:输入/输出操作失败(可通过clear()清除)
    • badbit:发射能够不可恢复错误(不可通过clear()清除)
流状态检测方法
方法 描述 方法 描述
eof() eofbit bad() badbit
good() goodbit fail() failbit || badbit
rdstate() 返回当前流状态 clear() 清除流状态错误

流状态检测示例

流状态检测
    // 不断读入整数直至读取失败
    std::cout << "请输入整数(Ctrl-D结束输入): ";
    for (int n; std::cin >> n;)
       std::cout << n << ' ';
    std::cout << '\n';
 
    if (std::cin.bad())
        std::cout << "发生不可恢复错误\n";
    else if (std::cin.eof())
        std::cout << "抵达文件尾部\n";
    else if (std::cin.fail())
请输入整数(Ctrl-D结束输入): 1 2 3 4 5 d
1 2 3 4 5 
非法输入
请输入整数(Ctrl-D结束输入): 1 
1 # Ctrl-D here
抵达文件尾部

惯用法

通常,C++ I/O循环使用I/O操作(如流提取运算符)的结果作为循环控制条件(第7行),该结果等价于!fail()

总结

本节内容

---
config:
  look: handDrawn
  themeVariables:
    fontSize: 24px
---
mindmap
  输入输出
    格式化输入输出
      格式化标志
        dec, oct, hex
        scientific, fixed
        left, right, internal
        boolalpha
        showbase, showpoint, showpos
        skipws
        uppercase
      格式化标志管理方法
        fill
        width
        precision
    IO操纵子
      dec, oct, hex
      scientific, fixed, hexfloat, defaultfloat
      left, right, internal
      boolalpha, noboolalpha
      showbase, noshowbase
      showpoint, noshowpoint
      showpos, noshowpos
      skipws, noskipws
      uppercase, nouppercase
      setw, setfill, setprecision
    输入输出错误处理
      流的状态
      流错误检测与清除

学习目标

  • 理解流式输入输出的内涵
  • 掌握格式化输入输出的两种方法,尤其是IO操纵子的使用方法
  • 检测并处理输入输出错误

课后作业

  • 实训(截止时间2025.03.19
    • C&C++基本输入输出实训
  • 预习
    • 教材5.1–5.4:函数
    • 教材5.6–5.7:作用域与存储类别

计算机程序设计