C++ 通过 <fstream> 头文件提供强大的文件输入/输出功能。它将文件视为一种“流”(stream),允许你像使用 cin 和 cout 一样方便地读写文件。
核心头文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include <fstream> ````
---
## 1. 核心类 (文件流对象)
在 `<fstream>` 中,你主要会用到三个类,它们是文件操作的“变量”:
- **`std::ifstream`** (Input File Stream) - **用途:** 用于从文件中**读取**数据。 - **类比:** 就像 `cin`,但数据源是文件。 - **`std::ofstream`** (Output File Stream) - **用途:** 用于向文件中**写入**数据。 - **类比:** 就像 `cout`,但目的地是文件。 - **`std::fstream`** (File Stream) - **用途:** 可以同时**读取和写入**同一个文件。 - **类比:** `cin` 和 `cout` 的结合体。
---
## 2. 核心函数与操作
### A. 打开文件
有两种方式打开文件:
**方式一:在构造时打开 (推荐)** ```cpp
std::ofstream outFile("my_file.txt");
std::ifstream inFile("my_file.txt");
|
方式二:使用 open() 成员函数
1 2 3 4 5
| std::ofstream outFile; outFile.open("my_file.txt");
std::ifstream inFile; inFile.open("my_file.txt");
|
B. 检查文件是否打开成功 (必须)
在读写之前,必须检查文件是否成功打开。如果文件不存在(读取时)或没有权限(写入时),操作会失败。
1 2 3 4 5 6 7 8 9 10 11 12 13
| std::ifstream inFile("my_file.txt");
if (!inFile.is_open()) { std::cerr << "错误:无法打开文件 my_file.txt" << std::endl; }
if (!inFile) { std::cerr << "错误:无法打开文件 my_file.txt" << std::endl; }
|
C. 文件打开模式 (File Modes)
在打开文件时,你可以指定“模式”来控制如何操作文件。模式通过 std::ios 命名空间访问,并使用 | (按位或) 组合。
std::ios::in:读模式(ifstream 默认)。
std::ios::out:写模式(ofstream 默认)。
std::ios::trunc:截断模式。在打开文件时清空所有内容(ofstream 默认)。
std::ios::app:追加模式。从文件末尾开始写入,不清空内容。
std::ios::binary:二进制模式(默认为文本模式)。
常见组合示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| std::ofstream outFile1("log.txt");
std::ofstream outFile2("log.txt", std::ios::app);
std::fstream file("data.dat", std::ios::in | std::ios::out);
std::ofstream binFile("data.bin", std::ios::out | std::ios::binary);
|
D. 关闭文件
1 2
| outFile.close(); inFile.close();
|
注意: 当 ifstream 或 ofstream 对象离开其作用域时(例如函数结束),其析构函数会自动调用 close()。因此,显式调用 close() 不是_必须_的,但在长函数中或需要立即释放文件句柄时是好习惯。
3. 常见用法 1: 文本文件 I/O (Text I/O)
文本 I/O 用于读写人类可读的字符数据(如 .txt, .csv, .md 文件)。
A. 写入文本文件 (使用 <<)
使用流插入运算符 <<,就像 cout 一样。
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <iostream> #include <fstream> #include <string>
int main() { std::ofstream outFile("config.txt");
if (!outFile) { std::cerr << "无法创建 config.txt" << std::endl; return 1; }
outFile << "AppName=SalarySystem" << std::endl; outFile << "Version=1.0" << std::endl; int userCount = 100; outFile << "UserCount=" << userCount << std::endl;
outFile.close(); std::cout << "配置文件写入成功。" << std::endl; return 0; }
|
config.txt 的内容:
1 2 3
| AppName=SalarySystem Version=1.0 UserCount=100
|
B. 读取文本文件 (使用 >> 和 getline)
方式一:使用 >> (按空格/换行分隔)
方式二:使用 std::getline() (按行读取)
实例 (结合两种方式):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include <iostream> #include <fstream> #include <string> #include <sstream>
int main() { std::ifstream inFile("employees.txt"); if (!inFile) { std::cerr << "无法打开 employees.txt" << std::endl; return 1; }
std::string type, name; int rank;
std::cout << "--- 方式一: 使用 >> 读取 ---" << std::endl; while (inFile >> type >> name >> rank) { std::cout << "类型: " << type << ", 姓名: " << name << ", 级别: " << rank << std::endl; }
inFile.close();
std::cout << "\n--- 方式二: 使用 getline 读取 ---" << std::endl; std::ifstream inFile2("employees.txt"); std::string line;
while (std::getline(inFile2, line)) { std::cout << "读取到整行: " << line << std::endl; std::stringstream ss(line); std::string line_type, line_name; int line_rank; if (ss >> line_type >> line_name >> line_rank) { std::cout << " (解析) 类型: " << line_type << ", 姓名: " << line_name << std::endl; } } inFile2.close(); return 0; }
|
4. 常见用法 2: 二进制文件 I/O (Binary I/O)
二进制 I/O 用于读写原始的字节数据。它速度更快、文件更小,但内容不是人类可读的。常用于保存 struct 或 class 的原始内存映像。
A. 关键函数 (write / read)
write(const char* buffer, streamsize count):将 buffer 指向的内存中的 count 个字节写入文件。
read(char* buffer, streamsize count):从文件中读取 count 个字节到 buffer 指向的内存。
注意: 你需要使用 reinterpret_cast 来将你的数据指针转换为 char*。
B. 实例:保存和读取结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #include <iostream> #include <fstream> #include <string>
struct Player { int id; char name[50]; double score; };
int main() { Player p1 = {101, "TestUser", 95.5}; Player p2_load;
std::ofstream outFile("player.dat", std::ios::binary); if (!outFile) { std::cerr << "无法创建 player.dat" << std::endl; return 1; }
outFile.write(reinterpret_cast<const char*>(&p1), sizeof(Player)); outFile.close(); std::cout << "二进制数据写入成功。" << std::endl;
std::ifstream inFile("player.dat", std::ios::binary); if (!inFile) { std::cerr << "无法读取 player.dat" << std::endl; return 1; }
inFile.read(reinterpret_cast<char*>(&p2_load), sizeof(Player)); inFile.close();
std::cout << "读取数据成功:" << std::endl; std::cout << "ID: " << p2_load.id << std::endl; std::cout << "Name: " << p2_load.name << std::endl; std::cout << "Score: " << p2_load.score << std::endl;
return 0; }
|
5. 重要陷阱与注意事项
⚠️ 陷阱:二进制 I/O 和 std::string / std::vector
你不能像上面的 Player 示例一样,直接对包含 std::string、std::vector 或其他指针成员的类使用 read/write。
原因: std::string 对象本身(在栈上)只包含一个指向堆内存的_指针_和一些大小信息。write 只会保存这个指针地址,而不是它指向的实际字符数据。当你 read 回来时,得到的是一个无效的指针,程序会崩溃。
对你项目的影响:
你的 Person 类有 std::string name 成员。因此,你不能像下面这样做:
1 2 3
| Manager m("Alice"); outFile.write(reinterpret_cast<const char*>(&m), sizeof(Manager));
|
结论: 对于你的项目,使用文本 I/O(即 << 和 >>)是正确且安全的选择。