Chapter 17:Input, Output, and Files

对齐第6版 Chapter 17:掌握 formatted output/input、stream state、file I/O、binary random access 与 string streams。

学习目标

  • 能实现 cout/cin 的格式化输出输入,比较 width、precision、basefield 与 getline 的持久/单次效果
  • 能解释 good/eof/fail/bad stream state,复现格式失败恢复、正常 EOF 和底层 I/O 错误路径
  • 能设计文本/二进制 file input/output、seekg/seekp random access 与 stringstream 解析,并验证记录布局和完整传输

机制总览

Chapter 17:Input, Output, and Files:机制路径

  1. 1

    为什么 I/O 的结果不只是“变量有了一个值”

    一次流操作同时改变三样东西:目标值或输出字符、输入/输出位置、stream state。只检查变量会漏掉未消费分隔符、部分读取与失败后旧值;只检查 EOF 又会把格式错误误报成正常结束。可靠 I/O 让操作成功本身驱动控制流。

  2. 2

    formatted output 是类型值到字符表示的策略

    格式化输出(formatted output)把数值、字符、指针等类型值转换成字符并插入 ostream。

  3. 3

    formatted input 从字符尝试建立类型值

    格式化输入(formatted input)用 operator 跳过规则允许的前导空白,读取能构成目标类型的字符,并停在第一个不属于该值的字符。成功后才可使用新值;失败时目标不应被当作新数据。

先按顺序建立机制,再进入实验切换阶段并检查失效证据。

章级决策实验

Chapter 17:Input, Output, and Files:机制与证据

切换《Chapter 17:Input, Output, and Files》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。

选择推理阶段

当前阶段 · 为什么 I/O 的结果不只是“变量有了一个值”

一次流操作同时改变三样东西:目标值或输出字符、输入/输出位置、stream state。只检查变量会漏掉未消费分隔符、部分读取与失败后旧值;只检查 EOF 又会把格式错误误报成正常结束。可靠 I/O 让操作成功本身驱动控制流。

可核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「为什么 I/O 的结果不只是“变量有了一个值”」的状态变化。

学完《Chapter 17:Input, Output, and Files》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。

失效—证据矩阵

Chapter 17:Input, Output, and Files:失效与核验

为什么 I/O 的结果不只是“变量有了一个值”

典型失效

若只复述「为什么 I/O 的结果不只是“变量有了一个值”」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「为什么 I/O 的结果不只是“变量有了一个值”」的状态变化。

formatted output 是类型值到字符表示的策略

典型失效

若只复述「formatted output 是类型值到字符表示的策略」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「formatted output 是类型值到字符表示的策略」的状态变化。

formatted input 从字符尝试建立类型值

典型失效

若只复述「formatted input 从字符尝试建立类型值」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「formatted input 从字符尝试建立类型值」的状态变化。

每个判断都必须能落到观测、测试或产物,不能只凭代码表面推测。

为什么 I/O 的结果不只是“变量有了一个值”

一次流操作同时改变三样东西:目标值或输出字符、输入/输出位置、stream state。只检查变量会漏掉未消费分隔符、部分读取与失败后旧值;只检查 EOF 又会把格式错误误报成正常结束。可靠 I/O 让操作成功本身驱动控制流。

formatted output 是类型值到字符表示的策略

格式化输出(formatted output)把数值、字符、指针等类型值转换成字符并插入 ostream。dec/hex/octfixed/scientificshowbase 等 flags 通常持续生效;setw 只影响下一字段;setfill 持续;precision 在 defaultfloat 与 fixed 下含义不同。

#include <iomanip>
#include <iostream>
 
std::cout << std::hex << std::showbase
          << std::setw(8) << std::setfill('0') << 255
          << '\n';
 
std::cout << std::dec << std::noshowbase << std::setfill(' ')
          << std::fixed << std::setprecision(2) << 12.345
          << '\n';

如果函数临时改变共享流 flags,应保存并恢复,避免影响后续调用者。格式控制是 stream 的可变状态,不是表达式局部注释。

formatted input 从字符尝试建立类型值

格式化输入(formatted input)用 operator>> 跳过规则允许的前导空白,读取能构成目标类型的字符,并停在第一个不属于该值的字符。成功后才可使用新值;失败时目标不应被当作新数据。

int value{};
while (std::cin >> value) {
    process(value);
}
 
if (std::cin.eof()) {
    std::cout << "normal end\n";
} else {
    std::cerr << "invalid token\n";
}

循环条件先尝试提取,只有成功才进入 body。while (!cin.eof()) 会在失败后多执行一次并可能复用旧 value。

getline 与 operator>> 需要统一分隔符协议

operator>> 读取整数后通常把换行留在缓冲区,紧接的 getline 会读到空行。可在切换为行协议时使用 std::getline(std::cin >> std::ws, line),但 ws 也会丢弃所有前导空白;若前导空白有意义,应显式消费恰好一个换行。

int count{};
std::string description;
 
if (std::cin >> count) {
    std::getline(std::cin >> std::ws, description);
}

正确选择取决于格式:token-based 文件适合 >>,行内允许空白的记录适合 getline 后用独立 parser。不要边读边猜协议。

stream state 是一组可组合状态位

流状态(stream state)包含 goodbit、eofbit、failbit、badbit。good() 仅在无错误位时真;fail() 覆盖 failbit/badbit;把 stream 用作布尔条件表示尚可继续格式化 I/O。EOF 可以与 failbit 同时出现,例如读取不完整目标。

int value{};
while (true) {
    if (std::cin >> value) break;
    if (std::cin.eof() || std::cin.bad()) {
        throw std::runtime_error{"input unavailable"};
    }
    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    std::cout << "enter an integer: ";
}

clear() 只重置状态位,不移除导致失败的字符;必须按输入协议 discard 或 repair。badbit 通常表示底层故障,不应像用户输错 token 一样盲目重试。

file input and output 要先验证打开与模式

文件输入输出使用 ifstream/ofstream/fstream。构造后先检查打开状态;缺失文件、权限和路径错误不能伪装成空内容。常见 mode 有 inoutappatetruncbinary,组合会影响创建、截断和位置。

#include <fstream>
 
std::ifstream input{path};
if (!input) {
    throw std::runtime_error{"cannot open " + path.string()};
}
 
std::string line;
while (std::getline(input, line)) {
    parseLine(line);
}
if (!input.eof()) {
    throw std::runtime_error{"read failure"};
}

输出重要文件可先写同目录临时文件,检查 flush/close 后再原子替换,避免中途失败破坏旧版本。析构会 close,但业务仍需在提交前观察写入错误。

binary file 保存字节,不保存跨平台对象语义

二进制文件(binary file)以 read/write 传输指定字节数,不做换行或数字文本转换。直接写普通 struct 内存会包含 padding,并依赖 endian、类型宽度、浮点表示和版本;含 pointer/string/virtual member 的对象更不能直接持久化。

struct DiskHeader {
    std::uint32_t magic;
    std::uint16_t version;
    std::uint16_t recordSize;
};
 
DiskHeader header{};
input.read(reinterpret_cast<char*>(&header), sizeof header);
if (!input || header.magic != expectedMagic) {
    throw std::runtime_error{"invalid header"};
}

示例只说明 byte transfer;真正可移植格式应逐字段编码固定宽度和字节序,并校验 magic、version、record length 与 checksum。

random access 同时管理 get 和 put position

随机访问使用 seekg/tellg 管理输入位置,seekp/tellp 管理输出位置,可相对 begin/current/end 移动。固定长度记录的第 i 项通常位于 header + i * recordSize,必须先验证乘法/范围不溢出且文件足够长。

std::fstream file{path, std::ios::in | std::ios::out | std::ios::binary};
const std::streamoff offset = headerSize + index * recordSize;
file.seekg(offset, std::ios::beg);
 
Record record{};
file.read(reinterpret_cast<char*>(&record), sizeof record);
if (!file) throw std::runtime_error{"short record"};

在读写混合的 fstream 中切换方向前应进行 seek/flush 等规定同步;app 会强制每次写到尾部,与任意位置更新冲突。

先预测文本顺序、二进制顺序、随机读和随机写各需哪一个 position/mode,再切换实验面板。seek 成功后仍要检查完整 read/write。

string streams 让字符串复用同一格式协议

istringstream 从 string 提取,ostringstream 向 string 格式化,stringstream 支持二者。它们适合逐行解析、构造日志和测试格式,不涉及文件打开。

#include <sstream>
 
std::istringstream row{"17 Ada 98.5"};
int id{};
std::string name;
double score{};
 
if (!(row >> id >> name >> score) || (row >> std::ws && !row.eof())) {
    throw std::runtime_error{"invalid row"};
}

解析成功还应决定是否允许尾随字段;上例消费尾空白后要求 EOF,形成完整记录契约。

三步验证 I/O 边界

分步1 / 3

第一步:记录值、字符与位置

为 formatted/unformatted 操作写输入值、格式 flags、产生/消费字符和操作后位置,检查持续性 manipulator。

小结

  • formatted output/input 同时受类型、格式策略、字符边界和 stream state 约束
  • eof 是读取尝试后的状态,循环应由读取成功驱动;clear 不会移除坏 token
  • file input and output 先检查打开、mode 与最终状态,重要输出应事务提交
  • binary file 只传字节,稳定格式必须定义 schema、endian、version 与长度
  • random access 分开管理 get/put position,seek 后仍要验证完整 transfer
  • string streams 复用格式协议,适合行级解析并可检查是否完整消费

练习

  1. 问题 1:修复读取循环。 while(!file.eof()){ file>>x; sum+=x; } 为什么会重复旧值?
  1. 问题 2:设计二进制格式。 为什么不能直接 write 一个含 string 的 Record 对象?
  1. 问题 3:验证随机更新。 更新第 i 条固定记录前后要检查哪些位置与状态?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

格式化输出
按进制、精度等策略把类型值转成字符。
流操纵符
修改格式状态或触发流动作的函数/对象。
格式化输入
按目标类型从字符解析值并更新状态。
流状态
记录成功、末尾、格式失败和底层损坏的状态位集合。
文件输入输出
用文件流按打开模式连接持久字节/字符序列。
二进制文件
按精确字节传输并需显式布局/版本的文件。
随机访问
移动 get/put position 后直接访问指定偏移。

资料与写作方式声明

本章以C++ Primer Plus, Sixth Edition, Chapter 17 Input, Output, and Files权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…