C++ 文件操作基于标准库 <fstream> ,以流(stream)为核心,提供面向对象的文件读写能力,比 C 语言的FILE*更安全、更灵活。本文从核心类、打开模式、基础操作、文本 / 二进制读写、指针操作、错误处理、高级功能 到最佳实践,全面覆盖所有关键知识点。
C++ 文件操作速查手册-CSDN博客
https://blog.csdn.net/Howrun777/article/details/157380224?spm=1011.2415.3001.5331
一、核心头文件与类
1. 核心头文件
cpp
#include <fstream> // 核心:文件流类(ifstream/ofstream/fstream)
#include <iostream> // 基础流(cin/cout),文件流继承自其基类
#include <string> // 字符串处理(文件路径、读写内容)
#include <cstdio> // 高级操作(重命名、删除文件,可选)
2. 三大核心文件流类
文件流类继承自标准 I/O 流基类,分工明确:
| 类名 | 继承关系 | 核心功能 | 适用场景 |
|---|---|---|---|
std::ifstream |
ios_base → ios → istream → ifstream |
只读文件 | 读取配置文件、日志、文本内容 |
std::ofstream |
ios_base → ios → ostream → ofstream |
只写文件 | 写入日志、生成报告、保存数据 |
std::fstream |
ios_base → ios → iostream → fstream |
读写文件 | 同时需要读 / 写的场景(如修改文件) |
关键:
ifstream/ofstream是单方向流,fstream是双向流,可同时读写。
二、文件打开模式(核心)
打开文件时需指定打开模式 (std::ios_base::openmode 类型),决定文件的操作方式,可通过 | 组合多个模式。
1. 常用模式常量
| 模式常量 | 含义 | 适用类 |
|---|---|---|
std::ios::in |
以只读方式打开,文件不存在则打开失败 | ifstream(默认)、fstream |
std::ios::out |
以只写 方式打开,文件不存在则创建,存在则清空内容 (默认含trunc) |
ofstream(默认)、fstream |
std::ios::app |
以追加方式写,写入内容添加到文件末尾,不清空原有内容 | ofstream、fstream |
std::ios::trunc |
打开时清空文件原有内容 (out 默认包含此模式) |
ofstream、fstream |
std::ios::binary |
以二进制模式打开(默认是文本模式),不处理换行符等特殊字符 | 所有文件流类 |
std::ios::ate |
打开文件后,文件指针直接定位到文件末尾(可后续移动指针) | 所有文件流类 |
std::ios::nocreate |
打开时不创建新文件,文件不存在则打开失败(部分编译器支持) | 所有文件流类 |
std::ios::noreplace |
打开时不覆盖已有文件,文件存在则打开失败(部分编译器支持) | 所有文件流类 |
2. 各流默认模式
ifstream:默认std::ios::in(只读)ofstream:默认std::ios::out | std::ios::trunc(只写 + 清空)fstream:默认std::ios::in | std::ios::out(读写,文件不存在则失败)
3. 模式组合示例
cpp
// 1. 只读文本文件(ifstream默认,可省略模式)
std::ifstream in("dict.conf");
// 2. 只写+追加文本文件(不清空原有内容)
std::ofstream out("log.txt", std::ios::out | std::ios::app);
// 3. 读写+二进制文件(用于图片、视频等)
std::fstream fs("data.bin", std::ios::in | std::ios::out | std::ios::binary);
// 4. 只写+不创建新文件(文件不存在则失败)
std::ofstream out("exist.txt", std::ios::out | std::ios::nocreate);
std::fstream in(_path);中的in是 **std::fstream类的实例对象 **,不是变量、指针或其他类型。- 这个对象封装了文件读取的所有状态和行为,是你操作文件的核心 "载体"。
- 你对文件的所有读取操作(检查打开状态、逐行读取、移动指针等),本质上都是通过调用
in这个对象的成员方法完成的。
三、文件操作基本流程
无论读 / 写,核心流程都是:打开文件 → 检查是否成功 → 读写操作 → 关闭文件。
1. 打开文件(两种方式)
方式 1:构造函数直接打开(推荐,RAII 风格)
cpp
// 只读打开
std::ifstream in("test.txt");
// 只写+追加打开
std::ofstream out("log.txt", std::ios::app);
// 读写+二进制打开
std::fstream fs("data.bin", std::ios::in | std::ios::out | std::ios::binary);
方式 2:先创建对象,再调用open()
cpp
std::ifstream in;
in.open("test.txt", std::ios::in); // 显式指定模式
2. 检查文件是否打开成功(必须!)
文件打开可能失败(路径错误、权限不足、文件不存在等),必须先检查:
cpp
std::ifstream in("test.txt");
if (!in.is_open()) { // 或直接写 if (!in),流对象可隐式转换为bool
std::cerr << "文件打开失败!路径:" << "test.txt" << std::endl;
return 1; // 异常退出
}
3. 关闭文件
- 显式关闭 :调用
close()方法,释放文件资源(推荐,尤其是长时间运行的程序)。 - 自动关闭 :流对象析构时 会自动调用
close()(RAII 特性,无需手动关也能释放资源)。
cpp
in.close(); // 显式关闭
out.close();
fs.close();
四、文本文件读写(最常用)
文本文件以字符 为单位读写,默认会处理换行符 (Windows:\r\n,Linux:\n),适合配置文件、日志、纯文本内容。
1. 文本读操作(ifstream/fstream)
(1)逐行读取(std::getline)
最常用,适合读取配置文件、日志等按行存储的内容:
cpp
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::ifstream in("dict.conf");
if (!in) {
std::cerr << "打开失败" << std::endl;
return 1;
}
std::string line;
// 逐行读取,直到文件末尾(getline返回流对象,eof时为false)
while (std::getline(in, line)) {
std::cout << "读取行:" << line << std::endl;
}
in.close();
return 0;
}
(2)按空白符分割读取(>> 运算符)
>> 会自动跳过空格、换行、制表符等空白符,适合读取单词、数字:
cpp
std::ifstream in("num.txt"); // 内容:10 20 30 40
int num;
while (in >> num) { // 每次读取一个整数
std::cout << "读取数字:" << num << std::endl;
}
// 输出:10 20 30 40
(3)单字符读取(get())
读取单个字符(包括空白符、换行符),适合逐字符处理:
cpp
std::ifstream in("test.txt");
char ch;
while (in.get(ch)) { // 读取单个字符
std::cout << ch; // 原样输出所有字符(包括换行、空格)
}
2. 文本写操作(ofstream/fstream)
(1)格式化写入(<< 运算符)
和std::cout用法一致,支持格式化输出(如整数、字符串、浮点数):
cpp
#include <iostream>
#include <fstream>
#include <string>
int main() {
// 只写+追加打开,不清空原有内容
std::ofstream out("log.txt", std::ios::app);
if (!out) {
std::cerr << "打开失败" << std::endl;
return 1;
}
// 写入字符串
out << "程序启动时间:2026-01-26 10:00:00" << std::endl;
// 写入整数+字符串
out << "用户ID:" << 1001 << " | 操作:登录" << std::endl;
// 写入浮点数
out << "温度:" << 25.5 << "℃" << std::endl;
out.close();
return 0;
}
(2)单字符写入(put())
写入单个字符,适合逐字符输出:
cpp
std::ofstream out("char.txt");
out.put('H');
out.put('e');
out.put('l');
out.put('l');
out.put('o');
out.put('\n'); // 写入换行符
// 文件内容:Hello
五、二进制文件读写
二进制文件以字节 为单位读写,不处理换行符等特殊字符 ,直接读写原始数据,适合图片、视频、可执行文件、自定义结构体等非文本数据。
1. 核心要求
- 打开时必须指定
std::ios::binary模式。 - 读写使用
read()和write()方法(而非>>/<<)。
2. 二进制读操作(read())
bash
// 函数签名:istream& read(char* buffer, std::streamsize size);
// buffer:存储读取数据的缓冲区(char* 类型)
// size:要读取的字节数
示例:读取整数(4 字节)
cpp
#include <iostream>
#include <fstream>
int main() {
// 二进制只读打开
std::ifstream in("data.bin", std::ios::in | std::ios::binary);
if (!in) {
std::cerr << "打开失败" << std::endl;
return 1;
}
int num;
// 读取4字节(int的大小)到num中
in.read(reinterpret_cast<char*>(&num), sizeof(num));
std::cout << "读取整数:" << num << std::endl;
in.close();
return 0;
}
3. 二进制写操作(write())
bash
// 函数签名:ostream& write(const char* buffer, std::streamsize size);
// buffer:要写入的数据缓冲区(const char* 类型)
// size:要写入的字节数
示例:写入整数 + 结构体
cpp
#include <iostream>
#include <fstream>
#include <string>
// 自定义结构体
struct Student {
int id;
char name[20];
float score;
};
int main() {
// 二进制只写打开
std::ofstream out("stu.bin", std::ios::out | std::ios::binary);
if (!out) {
std::cerr << "打开失败" << std::endl;
return 1;
}
// 1. 写入整数
int num = 12345;
out.write(reinterpret_cast<const char*>(&num), sizeof(num));
// 2. 写入结构体
Student stu = {1001, "张三", 95.5f};
out.write(reinterpret_cast<const char*>(&stu), sizeof(stu));
out.close();
return 0;
}
注意:
reinterpret_cast<char*>是强制类型转换,将任意类型指针转为char*(字节指针),是二进制读写的核心技巧。
六、文件指针与随机访问
文件流内部维护读指针 和写指针 (文本模式下两者通常同步,二进制模式下可独立操作),通过指针可实现随机访问(直接跳转到文件任意位置读写)。
1. 核心指针操作函数
| 函数 | 所属类 | 功能 | 备注 |
|---|---|---|---|
seekg(pos) |
istream |
移动读指针到绝对位置 | g=get(读) |
seekg(off, dir) |
istream |
移动读指针到相对位置 | off = 偏移量,dir = 基准位置 |
tellg() |
istream |
获取当前读指针位置 | 返回字节数(std::streampos) |
seekp(pos) |
ostream |
移动写指针到绝对位置 | p=put(写) |
seekp(off, dir) |
ostream |
移动写指针到相对位置 | 同 seekg |
tellp() |
ostream |
获取当前写指针位置 | 同 tellg |
cpp
open: 打开指定路径的文件,可指定打开模式
//C 风格字符串版本
void open(const char* filename, std::ios_base::openmode mode = /* 类默认模式 */);
//ifstream 默认模式:std::ios::in
//ofstream 默认模式:std::ios::out | std::ios::trunc
//fstream 默认模式:std::ios::in | std::ios::out
//C++ string 版本(C++11+)
void open(const std::string& filename, std::ios_base::openmode mode = /* 类默认模式 */);
close: 关闭文件,释放文件资源
void close();
is_open: 检查文件是否成功打开,返回 bool(必须优先调用)
bool is_open() const;
eof: 检查是否到达文件末尾(end of file),返回 bool
bool eof() const;
fail: 检查是否发生非致命错误(如格式错误),返回 bool
bool fail() const;
bad: 检查是否发生致命错误(如文件损坏),返回 bool
bool bad() const;
clear: 清除流的错误状态(如 eof () 为 true 后,需调用 clear () 才能继续操作)
void clear(std::ios_base::iostate state = std::ios_base::goodbit);
//state:要设置的流状态,默认清除所有错误(设为goodbit)
seekg: 移动读指针到指定位置(g=get)
//版本 1(绝对位置)
std::istream& seekg(std::streampos pos);
//版本 2(相对位置)
std::istream& seekg(std::streamoff off, std::ios_base::seekdir dir);
//off:偏移字节数
//dir:基准位置(ios::beg/ios::cur/ios::end)
seekp: 移动写指针到指定位置(p=put)
//版本 1(绝对位置)
std::ostream& seekp(std::streampos pos);
//版本 2(相对位置)
std::ostream& seekp(std::streamoff off, std::ios_base::seekdir dir);
tellg: 返回当前读指针的位置(字节数)
std::streampos tellg() const;
tellp: 返回当前写指针的位置(字节数)
std::streampos tellp() const;
2. 基准位置(std::ios_base::seekdir)
| 常量 | 含义 |
|---|---|
std::ios::beg |
从文件开头偏移 |
std::ios::cur |
从当前位置偏移 |
std::ios::end |
从文件末尾偏移 |
3. 随机访问示例
示例 1:读取文件末尾 10 字节
cpp
#include <iostream>
#include <fstream>
#include <vector>
int main() {
std::ifstream in("test.bin", std::ios::in | std::ios::binary);
if (!in) return 1;
// 1. 读指针移到文件末尾
in.seekg(0, std::ios::end);
// 2. 获取文件总大小(末尾位置的字节数)
std::streampos fileSize = in.tellg();
// 3. 读指针移到「末尾-10字节」的位置
in.seekg(fileSize - 10, std::ios::beg);
// 4. 读取10字节
std::vector<char> buffer(10);
in.read(buffer.data(), 10);
// 输出读取的内容
std::cout << "文件末尾10字节:";
for (char ch : buffer) std::cout << ch;
std::cout << std::endl;
in.close();
return 0;
}
示例 2:修改文件指定位置的内容
cpp
#include <iostream>
#include <fstream>
int main() {
// 读写+二进制打开
std::fstream fs("data.bin", std::ios::in | std::ios::out | std::ios::binary);
if (!fs) return 1;
// 1. 写指针移到第5字节位置(绝对位置)
fs.seekp(5);
// 2. 写入新数据(覆盖原位置内容)
int newNum = 999;
fs.write(reinterpret_cast<const char*>(&newNum), sizeof(newNum));
fs.close();
return 0;
}
七、流状态与错误处理
文件流操作可能出现各种错误(如文件尾、格式错误、文件损坏),C++ 提供流状态标志 和状态检查函数,用于判断和处理错误。
1. 流状态标志(std::ios_base::iostate)
| 标志位 | 含义 |
|---|---|
std::ios::goodbit |
流状态正常,无错误 |
std::ios::eofbit |
已到达文件末尾(EOF) |
std::ios::failbit |
发生非致命错误(如格式错误:读取整数时读到字母) |
std::ios::badbit |
发生致命错误(如文件损坏、磁盘故障、流被破坏) |
2. 状态检查函数
| 函数 | 功能 | 返回值 | 等价判断(标志位) | |
|---|---|---|---|---|
good() |
流状态是否正常 | bool | !eof() && !fail() && !bad() |
|
eof() |
是否到达文件末尾 | bool | (rdstate() & eofbit) != 0 |
|
fail() |
是否发生非致命错误 | bool | `(rdstate() & (failbit | badbit)) != 0` |
bad() |
是否发生致命错误 | bool | (rdstate() & badbit) != 0 |
3. 清除错误状态(clear())
当流出现错误(如eofbit/failbit)时,后续操作会失效,需调用clear()清除状态:
// 函数签名:void clear(std::ios_base::iostate state = std::ios::goodbit);
// 默认清除所有错误,设为goodbit
示例:完整错误处理流程
cpp
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::ifstream in("test.txt");
if (!in) {
std::cerr << "打开失败" << std::endl;
return 1;
}
std::string line;
while (std::getline(in, line)) {
// 正常处理每行内容
std::cout << line << std::endl;
}
// 读取结束后,判断错误类型
if (in.eof()) {
std::cout << "文件读取完成(正常到达末尾)" << std::endl;
} else if (in.fail()) {
std::cerr << "读取格式错误(非致命)" << std::endl;
} else if (in.bad()) {
std::cerr << "文件读取出错(致命,如磁盘故障)" << std::endl;
}
// 清除错误状态(如需后续操作)
in.clear();
// 重新定位到文件开头(可选)
in.seekg(0, std::ios::beg);
in.close();
return 0;
}
八、高级文件操作
1. 获取文件大小
通过读指针移到末尾 + tellg () 获取文件总字节数:
cpp
std::ifstream in("test.bin", std::ios::binary);
if (!in) return 1;
in.seekg(0, std::ios::end); // 移到末尾
std::streampos fileSize = in.tellg(); // 获取大小(字节)
in.seekg(0, std::ios::beg); // 移回开头
std::cout << "文件大小:" << fileSize << " 字节" << std::endl;
2. 文件重命名与删除(<cstdio>)
C++ 标准库未直接提供文件重命名 / 删除函数,需使用 C 标准库的std::rename()和std::remove():
cpp
#include <cstdio> // 必须包含
// 重命名文件:rename(旧路径, 新路径)
if (std::rename("old.txt", "new.txt") != 0) {
std::cerr << "重命名失败" << std::endl;
}
// 删除文件:remove(文件路径)
if (std::remove("delete.txt") != 0) {
std::cerr << "删除失败" << std::endl;
}
3. 临时文件
临时文件用于临时存储数据,程序结束后自动删除,有两种方式:
方式 1:std::tmpfile()(C 标准库,自动删除)
cpp
#include <cstdio>
// 创建临时文件(二进制读写,程序结束自动删除)
FILE* tmpFile = std::tmpfile();
if (!tmpFile) {
std::cerr << "创建临时文件失败" << std::endl;
return 1;
}
// 写入临时文件
std::fputs("临时数据", tmpFile);
// 读取临时文件
std::rewind(tmpFile); // 移到文件开头
char buffer[100];
std::fgets(buffer, 100, tmpFile);
std::cout << "临时文件内容:" << buffer << std::endl;
// 无需手动关闭,程序结束自动删除
方式 2:fstream创建临时文件(手动管理)
cpp
std::string tmpPath = "tmp_" + std::to_string(rand()) + ".tmp";
std::ofstream tmpOut(tmpPath);
if (tmpOut) {
tmpOut << "临时数据" << std::endl;
tmpOut.close();
// 使用临时文件...
// 手动删除
std::remove(tmpPath.c_str());
}
九、注意事项与最佳实践
1. 路径问题
- 相对路径 :相对于程序运行目录 (而非代码文件目录),如
./dict.conf(当前目录)、../conf/dict.conf(上级目录)。 - 绝对路径 :如
C:/test/dict.conf(Windows)、/home/test/dict.conf(Linux),稳定性高但移植性差。 - 跨平台路径分隔符 :优先使用
/(Windows 和 Linux 均支持),避免\(Windows 专属,需转义为\\)。
2. 资源管理(RAII)
- 优先使用构造函数打开文件 ,流对象析构时自动关闭,无需手动调用
close()(但显式调用更规范)。 - 避免手动管理
FILE*,优先使用 C++ 文件流(更安全,无内存泄漏风险)。
3. 权限问题
- 写文件时,程序需拥有目标目录的写入权限 ;读文件时,需拥有文件的读取权限。
- Windows 下需避免写入系统目录(如
C:\Windows),Linux 下需避免写入/root等特权目录。
4. 编码问题
- 文本文件默认编码:Windows 为 ANSI(GBK),Linux 为 UTF-8,跨平台需统一为UTF-8。
- 读写 UTF-8 文件时,无需特殊处理(C++ 流默认支持 UTF-8),但需注意中文在不同编码下的字节数差异。
5. 性能优化
- 大文件读写:优先使用二进制模式 + 缓冲区 (如
std::vector<char>),减少 IO 次数。 - 避免频繁打开 / 关闭文件:长时间操作的文件,打开一次即可,用完再关闭。
6. 错误处理
- 必须检查
is_open(),否则后续操作会导致程序崩溃。 - 读取后检查流状态,区分
eof(正常结束)和fail/bad(错误结束)。 - 清除错误状态后,需重新定位文件指针(如
seekg(0))才能继续操作。
7. C++11+ 新特性
std::string版本的open():支持直接传入std::string路径(无需转const char*)。- 移动语义:文件流支持移动构造 / 赋值,可高效传递流对象(避免拷贝)。
std::filesystem(C++17):提供更强大的文件系统操作(如遍历目录、获取文件属性),替代部分<cstdio>功能。
十、总结
C++ 文件操作的核心是流对象 + 打开模式 + 读写方法,核心逻辑可概括为:
- 选对类:
ifstream(读)、ofstream(写)、fstream(读写)。 - 选对模式:根据需求组合
in/out/app/binary等。 - 规范流程:打开→检查→读写→关闭,做好错误处理。
- 区分场景:文本文件用
>>/getline/<<,二进制文件用read/write+binary模式。 - 优化性能:大文件用二进制 + 缓冲区,避免频繁 IO。
掌握以上知识点,即可应对 C++ 中所有文件操作场景(配置读取、日志写入、数据存储、二进制处理等)。
常用场景示例
1. 逐行读取文本文件
cpp
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
int main() {
ifstream in("config.txt");
if (!in) { cerr << "打开失败"; return 1; }
string line;
while (getline(in, line)) {
cout << "读取行:" << line << endl;
}
in.close();
return 0;
}
2. 二进制读写结构体
cpp
#include <fstream>
#include <iostream>
using namespace std;
struct Student { int id; char name[20]; float score; };
int main() {
// 写入
ofstream out("stu.bin", ios::out | ios::binary);
Student s = {1001, "张三", 95.5};
out.write((char*)&s, sizeof(s));
out.close();
// 读取
ifstream in("stu.bin", ios::in | ios::binary);
Student s_read;
in.read((char*)&s_read, sizeof(s_read));
cout << "ID:" << s_read.id << " 姓名:" << s_read.name << " 分数:" << s_read.score << endl;
in.close();
return 0;
}