Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

文件与流(Files and Streams)

在 C++ 中,流(Stream) 是输入输出系统的核心概念。所有数据的读写操作——无论来自键盘、内存、文件还是网络——都被统一抽象为“流”的形式。 从概念上讲,文件是流的一种具体实现:流是一种数据传输通道,而文件流则是通向磁盘文件的通道。

一、流的基本概念

在程序运行过程中,数据在设备之间不断流动。C++ 将数据的输入输出过程抽象为一个“流(stream)”,即:

数据在内存与外部设备之间的有序传输。

流有两种基本方向:

类型类名说明
输入流istream数据从设备流入程序(例如键盘输入、文件读取)
输出流ostream数据从程序流向设备(例如屏幕输出、文件写入)

C++ 的标准输入输出(如 cincoutcerrclog)都是基于这套流机制实现的。

二、C++ 流类层次结构

C++ 标准库为不同的数据来源提供了不同种类的流类,这些类共同组成一个继承体系:

ios_base
 └── ios
      ├── istream          // 输入流
      │    ├── ifstream     // 文件输入流
      │    └── istringstream// 字符串输入流
      ├── ostream          // 输出流
      │    ├── ofstream     // 文件输出流
      │    └── ostringstream// 字符串输出流
      └── iostream         // 输入输出流
           ├── fstream      // 文件输入输出流
           └── stringstream // 字符串输入输出流

可以看到,无论是文件流、字符串流还是标准流,它们都共享相同的接口和操作方式。 因此,掌握流的基本用法,就能轻松在不同输入输出介质之间迁移代码。

三、流的分类与用途

类型头文件主要类典型用途
标准输入输出流<iostream>cin, cout, cerr, clog控制台输入输出
文件流<fstream>ifstream, ofstream, fstream读取与写入文件
字符串流<sstream>istringstream, ostringstream, stringstream内存中字符串格式化与解析

四、文件作为流的体现

在操作文件时,我们使用 ifstreamofstreamfstream 来打开磁盘文件并执行读写。 但本质上,这些类并没有引入新的 I/O 模型,而是继承自 istream / ostream,仅仅改变了流的来源或去向

  • ifstream:从文件读取数据(输入流)
  • ofstream:向文件写入数据(输出流)
  • fstream:既可读也可写(双向流)

这种统一的流模型让文件操作与普通输入输出完全一致:

std::ifstream fin("input.txt");
std::ofstream fout("output.txt");

int x;
fin >> x;       // 从文件读取
fout << x * 2;  // 写入文件

同时,为了适应多种文件类型,还支持基于二进制操作文件流:

// 二进制写入
std::ofstream fout_bin("data.bin", std::ios::binary);
int x = 42;
fout_bin.write(reinterpret_cast<const char*>(&x), sizeof(x));

五、字符串流的作用

<sstream> 提供了面向内存字符串的流操作。 它们与文件流类似,但数据读写的目标是内存字符串而非磁盘文件,非常适合:

  • 格式化文本(如将数值转为字符串)
  • 从字符串中提取结构化数据
  • 临时缓冲输出内容
std::stringstream ss;
ss << "Result: " << 42;
std::string text = ss.str();  // "Result: 42"

早期 C++ 还提供 <strstream> 实现基于字符数组的流,但由于安全性和内存管理问题,现已由 <sstream> 完全取代。

六、流状态与错误处理

在 C++ 的流系统中,无论是标准输入输出流、文件流还是字符串流,都共享一套统一的状态机制。 每个流对象都维护着一个内部状态,用于反映当前输入输出操作的健康状况。程序可以通过这些状态来判断流是否处于可用、结束或错误状态,从而实现可靠的错误控制。

1. 状态标志(Stream State Flags)

C++ 通过四种主要的状态标志来描述流的状态,这些标志可能同时存在,用于表达复杂情况:

状态名成员常量含义
goodbitstd::ios::goodbit一切正常,流处于可用状态
eofbitstd::ios::eofbit已到达输入结束(End Of File)
failbitstd::ios::failbit输入失败,通常是格式不匹配(如期望数字却读到字符)
badbitstd::ios::badbit流已损坏,通常是严重的系统性错误(如设备失效)

流的状态存储在 std::ios 基类中,因此所有继承自它的类(如 istreamostreamfstreamstringstream)都拥有相同的状态接口。

2. 状态检查接口

流对象提供了多种方式用于检查状态:

函数返回值说明
good()true / false流是否处于正常状态
eof()true / false是否到达文件或输入末尾
fail()true / false是否发生输入失败
bad()true / false是否出现系统性错误
rdstate()iostate返回全部状态标志的组合

例如:

int x;
std::cin >> x;
if (std::cin.fail()) {
    std::cerr << "输入错误:类型不匹配。" << std::endl;
}

当用户输入非数字时,cin.fail() 将为 true,表示提取操作失败。

3. 状态恢复与忽略输入

一旦流进入错误状态,后续输入输出操作将被阻塞。要恢复流,需要手动清除错误标志并可能丢弃无效输入:

std::cin.clear(); // 清除所有错误标志
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 忽略当前行

这段代码常用于防止错误输入导致程序陷入死循环,是交互式程序中非常典型的输入修复模式。

4. 状态机制的意义

C++ 的流状态机制让输入输出更具鲁棒性和通用性。 无论是键盘输入、文件读取,还是内存字符串解析,都可以通过相同的方式检测和处理异常。这种设计体现了“统一的流模型”思想:

  • 所有流对象共享同一组状态接口;
  • 程序可根据状态灵活决定后续逻辑;
  • 错误恢复无需依赖具体 I/O 类型。

这种一致性为大型项目中的 I/O 管理提供了强大的可移植性与可扩展性。

总结

“文件与流”既是 C++ I/O 系统的核心概念,也是工程实践中最常用的技术之一:

  • 日志系统基于文件流;
  • 配置文件解析常通过字符串流;
  • 网络传输底层也可抽象为流。

掌握流的思想,意味着可以用同一套接口处理不同数据源,为程序的输入输出设计提供统一模型。