跳至内容
Topos Uranos

摘要

输入是世界进入程序的边界,而世界并不遵守规格说明。本课教人不加信任地读取:用 std::getline 读取整行,并避免它与 >> 混用时出现的空行;查询流的状态,并借此区分输入、输出与错误一课只见到混在一起的三种情形(无效数据、放不下的数和数据的结束);用 clear 和 ignore 从失败的读取中恢复;以及,作为最安全的做法,把每个数据当作文本读取,再用 std::from_chars 转换,只有当转换用尽了整段文本时才接受它。本课最后给出一个带契约的函数 readInt,课程的其余部分可以重复使用它。

学习目标

  1. 用 std::getline 读取整行,解释为什么紧跟在 >> 读取之后的 getline 会返回空行,并用 std::ws 避免这种情况。
  2. 在读取之后用 fail() 和 eof() 查询流的状态,并借此区分无效数据、放不进其类型的数和输入的结束。
  3. 编写一个读取循环,它用 clear 和 ignore 从无效输入中恢复,并在输入结束时终止。
  4. 用 std::from_chars 校验作为文本读入的数,要求没有错误并且用尽整个字符串,并把校验封装在带契约的函数中。

再谈边界

输入、输出与错误一课确立了关于 std::cin >> n 的三个事实:读取在第一个不能延续该数的字符处停下,并且不消耗它;如果一个字符也没能读到,流就进入错误状态,此后的每次读取都什么也不做;而 if (std::cin) 可以让人知道是否有什么失败了。循环一课又补充说,条件 while (std::cin >> value) 无论是输入结束还是遇到无效数据都会终止。然而,有了这些工具,程序能察觉出了问题,却不知道是什么问题,除了放弃也做不了别的。

本课从一条设计原则出发,即边界上的防御式编程:每个进入的数据在使用之前都要校验,而校验它的程序要把允许纠正的无效数据(再要一次、跳过、报告它)与不允许纠正的数据结束区分开来。既然如此,就需要三样东西:按数据写下的样子读取它,也就是读取一行;知道一次读取为什么失败;以及让流恢复读取的能力。