在 C++ 中,文件操作主要依赖于标准库中的 <fstream> 头文件,它提供了三个核心类来处理文件,分别是 输入文件流**ifstream** (读取文件)、输出文件流**ofstream** (写入文件)、输入/输出文件流**fstream**(同时读写文件)。
1. 打开、关闭文件
1)打开文件前的准备工作:定义好文件路径(及多字节路径)和 打开方式。
在 C++ 字符串中 \ 是转义字符 ,使用双反斜杠 \\ 或正斜杠 / 表示路径。如果路径中包含中文,标准 std::string(UTF-8/GBK)直接传给 fstream 在 Windows 下极易导致打开失败。在 Windows 下应使用宽字符 std::wstring 和 std::wfstream,或者在 C++17 及以上版本中使用 std::filesystem::path。
cpp
std::string path1 = "C:\\data\\config.txt"; // 双反斜杠
std::string path2 = "C:/data/config.txt"; // 正斜杠(现代 C++ 更推荐,跨平台更好)
#include <filesystem>
namespace fs = std::filesystem;
fs::path filePath = u8"D:\\测试\\数据.txt"; // 使用 utf-8 字面量
std::ifstream file(filePath); // fstream 原生支持 fs::path
打开文件的方式有读、写、文件末尾追加、清空、二进制等基本方式,日常使用可多种方式结合。
cpp
// 文件标志位
std::ios::in: 读
std::ios::out: 写(默认清空)
std::ios::app: 追加(保留原内容,指针强制在尾部)
std::ios::trunc: 清空
std::ios::binary: 二进制(禁止系统自动转换 \n 为 \r\n)
//组合方式
ios::out | ios::trunc 文件不存在则创建;存在则清空 覆盖式配置写入、初始化日志
ios::out | ios::app 文件不存在则创建;存在则在末尾追加 日记记录、运行流水账记录
ios::in | ios::out 文件必须存在,允许读写,不自动清空 修改已有文件的特定位置(如数据库索引)
ios::in | ios::out | ios::trunc 文件不存在则创建;存在则清空,允许读写 临时缓冲区文件
2. 读、写文件
文件管理函数有open()、is_open()、close(),负责文件与程序之间的通道建立、断开和状态查询。
cpp
void open(const string& filename, openmode mode); 显式打开指定文件。如果流已经打开了一个文件,此操作会直接失败并置 failbit。
bool is_open() const; 检查是否成功打开文件。返回 true 表示通道正常。
void close(); 关闭文件,释放操作系统句柄。对写文件流,它会隐式触发刷盘(flush),如果磁盘满等原因导致刷盘失败,流会置 failbit。
文本文件读写成员函数:以字符或字符串为单位处理文本数据。
cpp
1. 流插入/流提取运算符:<< 、 >>
格式化读写:自动进行类型转换(int -> string 写入,或string -> float 读出)。
>> 默认会跳过所有空白字符(空格、制表符、换行符)。如果想连空格一起读,不能用它。
istream& get(char& ch); //从文件中读取单个字符,包括空格和换行符。常用于文件复制或逐字符解析。
istream& getline(char* s, streamsize n, char delim = '\n');
//这是流对象的成员函数,用于读取底层 C 风格字符串(char[]),遇到定界符 delim(默认换行)或读取了 n-1 个字符后结束。
ostream& put(char ch); //向文件写入单个字符 ch
二进制文件读写成员函数:数据不经过任何文本转换,原封不动地在内存和磁盘之间拷贝。
cpp
ostream& write(const char* s, streamsize n); //从内存地址 s 开始,连续写入 n 个字节的数据到文件中。 常用于直接将结构体、数组、整型变量的内存镜像保存到磁盘。
istream& read(char* s, streamsize n); //从文件中读取 n 个字节,存入以 s 为起始地址的内存中。
streamsize gcount() const; //返回上一次非格式化输入函数(如 read(), get(), getline())实际成功读取的字节数/字符数。常用于判断最后一次读二进制块时到底读到了多少数据。
文件指针定位与随机访问函数:每个流对象内部都有一个记录读写位置的指针。输入流(Get)和输出流(Put)的指针是分开管理的(在 fstream 中它们独立存在但有时会联动)。
cpp
streampos tellg(); (Get)://返回输入流指针当前指向文件的第几个字节。
streampos tellp(); (Put)://返回输出流指针当前指向文件的第几个字节。
istream& seekg(streampos pos);//绝对定位,直接跳到文件的第 pos 个字节。
istream& seekg(streamoff off, ios_base::seekdir dir);//相对定位。off:偏移量(可正可负),dir:相对基准,可选值:ios::beg(文件开头),ios::cur(当前位置),ios::end(文件末尾)
ostream& seekp(streampos pos); / seekp(streamoff off, seekdir dir);//对输出流指针做同样的操作。
流状态与异常处理成员函数:流内部有一个状态字(通常是几位二进制),记录当前流是否健康。
在 C++ 文件流操作中,流状态(Stream State)管理是整个输入/输出体系的灵魂。
当程序在读写文件时,底层发生的任何情况(如读到末尾、类型不匹配、硬件故障)都会实时反映在流的状态位中。C++ 标准库使用一个状态掩码 iostate(通常是内部定义的位图标志)来记录状态。下面我们逐一拆解这些核心成员函数、它们的函数原型、返回值、以及触发条件的底层机制。
流内部的 4 个基础状态常量,它们定义在 std::ios_base 中。流完全正常:goodbit (0000)、到达文件末尾:eofbit (0001)、发生逻辑错误可恢复:failbit (0010)、发生严重物理错误不可恢复:badbit (0100)。
流状态查询成员函数
cpp
bool good() const; //返回true(当且仅当没有任何错误标志位被置位时);否则返回 false。
//触发与应用条件:只有在 rdstate() == goodbit 时,此函数才返回 true。
bool eof() const; //返回true(流已经越过了文件的最后一个字符);否则返回 false。
//触发条件:只有当上一次读取操作尝试去读越过文件末尾(EOF)的字节,且读不到数据时,eofbit 才会被点亮。
bool fail() const; //返回true(当 failbit 或 badbit 任意一个被点亮时);否则返回 false。
//触发条件:格式解析失败(最常见):文件里存的是字符串 "abc",代码却尝试执行 int n; file >> n;。此时类型不匹配,触发 failbit。打开文件失败:调用 file.open() 时,若文件不存在或无权限,会直接触发 failbit。fail() 为真意味着发生了逻辑错误,但文件通道本身可能没坏,清理掉脏数据并重置状态后,流还可以继续用。
bool bad() const; //返回true(底层 badbit 被点亮时);否则返回 false。
//触发条件:无法恢复的物理错误:在写入中途,磁盘空间满了(Disk Full);或者读取存储在 U 盘上的文件时,U 盘被强行拔出。流的内部缓冲区(streambuf)分配内存失败或发生严重流失控。
iostate rdstate() const; //返回 std::ios_base::iostate 类型的位掩码(组合值),代表当前流的所有状态。
//触发条件:精确组合判断时(例如既想知道是否到末尾,又想知道是否出错)
void clear(iostate state = goodbit); //
示例
cpp
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
// 1. 文本文件处理函数(写入与读取)
void handleTextFile() {
std::cout << "--- 开始文本文件操作 ---" << std::endl;
const std::string textFilename = "sample_text.txt";
std::ofstream outFile(textFilename, std::ios::out | std::ios::trunc);
if (!outFile.is_open()) { // 判断是否成功打开
std::cerr << "【错误】无法打开文本文件进行写入!" << std::endl;
return;
}
// 写入数据并判断是否成功
outFile << "Line 1: Hello C++ File I/O.\n";
outFile << "Line 2: Test textual data.\n";
if (!outFile.good()) {
std::cerr << "【错误】文本文件写入过程中发生错误!" << std::endl;
outFile.close();
return;
}
// 关闭文件并校验
outFile.close();
if (outFile.fail()) {
std::cerr << "【错误】文本文件关闭(刷盘)失败!" << std::endl;
return;
}
std::cout << "文本文件写入并安全关闭成功。" << std::endl;
// ---------------- [读取阶段] ----------------
std::ifstream inFile(textFilename, std::ios::in);
if (!inFile.is_open()) {
std::cerr << "【错误】无法打开文本文件进行读取!" << std::endl;
return;
}
std::string line;
// 正确的循环判定:直接将 getline 放进条件中, 它能同时准确判断"是否读到末尾"和"是否中途出错"
while (std::getline(inFile, line)) {
std::cout << " [读出文本]: " << line << std::endl;
}
// 退出循环后,审计原因
if (inFile.eof()) {
std::cout << "【状态】文本文件已成功、完整地读到末尾。" << std::endl;
} else if (inFile.fail()) {
std::cerr << "【错误】文本文件读取中途因格式或逻辑错误中断!" << std::endl;
}
inFile.close();
}
// 2. 二进制文件处理函数(逐字节写入与读取)
void handleBinaryFile() {
std::cout << "\n--- 开始二进制文件操作 ---" << std::endl;
const std::string binFilename = "sample_binary.dat";
// 准备一些模拟的原始二进制数据 (4个字节)
std::vector<uint8_t> writeData = {0xDE, 0xAD, 0xBE, 0xEF};
// ---------------- [写入阶段] ----------------
std::ofstream outFile(binFilename, std::ios::out | std::ios::binary | std::ios::trunc);
if (!outFile.is_open()) {
std::cerr << "【错误】无法打开二进制文件进行写入!" << std::endl;
return;
}
// 逐字节写入
for (size_t i = 0; i < writeData.size(); ++i) {
outFile.write(reinterpret_cast<const char*>(&writeData[i]), 1);
// 每写一次都校验流状态
if (!outFile.good()) {
std::cerr << "【错误】二进制逐字节写入时在位置 " << i << " 发生异常!" << std::endl;
outFile.close();
return;
}
}
outFile.close();
if (outFile.fail()) {
std::cerr << "【错误】二进制文件关闭失败!" << std::endl;
return;
}
std::cout << "二进制文件逐字节写入并安全关闭成功。" << std::endl;
// ---------------- [读取阶段] ----------------
std::ifstream inFile(binFilename, std::ios::in | std::ios::binary);
if (!inFile.is_open()) {
std::cerr << "【错误】无法打开二进制文件进行读取!" << std::endl;
return;
}
std::cout << " [读出二进制数据 (1字节为单位)]:" << std::endl;
char byteBuffer; // 用于承载 1 字节的缓冲区
// 循环判定:使用 read() 尝试读取 1 字节,read() 返回流对象的引用,只要成功读取,流状态就为 good,循环继续
while (inFile.read(&byteBuffer, 1)) {
// gcount() 成员函数可以二次确认上一次确实读到了 1 个字节
if (inFile.gcount() == 1) {
// 以十六进制格式打印输出查看
std::cout << " 字节数据: 0x" << std::hex << std::uppercase
<< (static_cast<int>(byteBuffer) & 0xFF) << std::endl;
}
}
std::cout << std::dec; // 恢复标准输出的流格式(防止影响后续输出)
if (inFile.eof()) { // 退出循环后的精细化状态审计
std::cout << "【状态】二进制文件已成功、完整地读到末尾。" << std::endl;
} else if (inFile.fail()) {
std::cerr << "【错误】二进制文件读取因中途损坏或错误中断!" << std::endl;
}
inFile.close();
}
int main() {
handleTextFile();
handleBinaryFile();
return 0;
}