89 / 103 · C++11 · 约 13 分钟
输入解析:读完整记录,验证后再提交
从流中读出一个整数,并不证明整条记录合法。把逐行获取、字段提取、范围校验和结果提交分成阶段,让多余字段、负值与坏格式都留下可解释的失败结果,且不污染上一条有效记录。
Programming: Principles and Practice Using C++
已读第二版全部章节0—27和附录A—E的可用文字、代码、Drill、Review、Exercises、术语/索引;其中第1、11、22—25、27章与Glossary另用作者完整章节PDF阅读。作者章节共294印刷页;第11/22/23章关键缺页、表和图已补读图像。其余公开全书OCR仍有损坏代码与未恢复的图形/表格,特别第12—16章图形与GUI,因此不声称原版图文无缺口通读;习题未运行。
查看版本、实际阅读范围与原文入口 →一个成功提取不等于一条合法记录
输入流能把文本转换成类型,却不知道业务要求一行包含几个字段。若只读取名称和数量,字符串 alpha 3 extra 的前两个字段仍能成功提取;若只接受非负数量,整数提取也不会替你排除负数。书中由输入错误走向类不变量,关键正是把这些层次分开。
本例规定一行恰有一个不含空白的名称和一个非负整数,允许行尾空白,不接受额外字段。调用者负责取得整行,解析函数只处理这条有限记录。因此坏记录不会把共享输入流停在半个字段上,也不会把下一行误读成当前记录的补充。
先读候选值,再检查结束位置
函数把字段提取到局部变量,任一失败都立即返回 false。随后用 std::ws 消费允许的尾部空白,并检查是否已到记录末尾。这样数值后缀、额外字段与不完整输入都被拒绝,而换行获取与字段语法仍相互独立。
只有全部检查通过才交换名称并写入整数。前面的字符串分配若抛异常,旧记录也没有变化;默认分配器 string 的交换和 int 赋值在这里不会引入新的分配失败。这个简单提交步骤支持明确的失败不修改承诺,不需要在每个错误分支手工恢复旧字段。
错误策略应留给知道上下文的层
解析函数用布尔结果报告语法或范围不合法,调用层可以显示错误、跳过记录或要求重输;资源耗尽等异常仍自然传播,不把所有失败混成零数量。示例逐一比较坏输入之后的完整旧值,不能只检查“返回了 false”。
PPP 第二版使用 C++11 和部分 C++14 技术,图形库是教材对 FLTK 的封装,不是标准图形接口。这里选择标准字符串流,避免依赖教材头文件和图形环境。C++20 中还可以采用其他解析设施,但无论工具如何改变,字段数量、数值范围、输入结束和提交时点仍需由接口明确规定。
容易答错的地方
- 在循环里先检查 eof 再读取,会漏判或多处理一次;应先检查提取操作的实际结果。
- 不要逐字段直接修改输出对象;第二字段失败时可能留下看似有效的半条新记录。
运行一个例子
最低标准 C++11 · 完整程序 · 下载 .cpp
#include <cassert>
#include <sstream>
#include <string>
struct Record { std::string name; int count; };
bool parse_record(const std::string& line, Record& out) {
std::istringstream input(line);
std::string name;
int count = 0;
if (!(input >> name >> count) || count < 0) return false;
input >> std::ws;
if (!input.eof()) return false;
out.name.swap(name);
out.count = count;
return true;
}
int main() {
Record record{"old", 8};
assert(parse_record("alpha 3 ", record));
assert(record.name == "alpha" && record.count == 3);
for (const std::string line : {"beta -1", "beta 2 extra", "beta 2x", "beta", ""}) {
assert(!parse_record(line, record));
assert(record.name == "alpha" && record.count == 3);
}
assert(parse_record("zero 0", record));
assert(record.name == "zero" && record.count == 0);
}
在本地编译
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread books-programming-principles-practice.cpp -o example && ./example预期结果
预期:正常退出、无输出;所有 assert 通过。
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
把数量限制为0至100,并给出失败保持旧值的检查。
查看参考答案
在局部变量提取后,把范围条件改成 count < 0 || count > 100;提交代码不变。先解析 good 100 并确认成功,再解析 bad 101,要求返回 false 且名称仍为good、数量仍为100;最后解析 good 0 验证另一边界。不要提交后再把超限数量改回100,那会掩盖输入错误。
继续查证
标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。