[数据结构·C++语法] 文件IO核心指南
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
````
---
## 1. 核心类 (文件流对象)
在 `<fstream>` 中,你主要会用到三个类,它们是文件操作的“变量”:
- **`std::ifstream`** (Input File Stream)
- **用途:** 用于从文件中**读取**数据。
- **类比:** 就像 `cin`,但数据源是文件。
- **`std::ofstream`** (Output File Stream)
- **用途:** 用于向文件中**写入**数据。
- **类比:** 就像 `cout`,但目的地是文件。
- **`std::fstream`** (File Stream)
- **用途:** 可以同时**读取和写入**同一个文件。
- **类比:** `cin` 和 `cout` 的结合体。
---
## 2. 核心函数与操作
### A. 打开文件
有两种方式打开文件:
**方式一:在构造时打开 (推荐)**
```cpp
// 1. 打开文件用于写入 (默认会清空文件内容)
std::ofstream outFile("my_file.txt");
// 2. 打开文件用于读取
std::ifstream inFile("my_file.txt");
方式二:使用 open() 成员函数1
2
3
4
5std::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
13std::ifstream inFile("my_file.txt");
// 方式一:使用 .is_open()
if (!inFile.is_open()) {
std::cerr << "错误:无法打开文件 my_file.txt" << std::endl;
// 在此 return 或 exit
}
// 方式二:直接在 if 语句中检查 (更简洁)
// 文件流对象在布尔上下文中会自动转换为 true (如果成功) 或 false (如果失败)
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// 1. 写入文件 (清空原内容) - 默认模式
std::ofstream outFile1("log.txt");
// 等同于: std::ofstream outFile1("log.txt", std::ios::out | std::ios::trunc);
// 2. 向文件末尾追加内容
std::ofstream outFile2("log.txt", std::ios::app);
// 等同于: std::ofstream outFile2("log.txt", std::ios::out | std::ios::app);
// 3. 打开文件用于读和写
std::fstream file("data.dat", std::ios::in | std::ios::out);
// 4. 打开二进制文件用于写入
std::ofstream binFile("data.bin", std::ios::out | std::ios::binary);
D. 关闭文件
1 | outFile.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
int main() {
std::ofstream outFile("config.txt"); // 打开文件 (默认清空)
if (!outFile) {
std::cerr << "无法创建 config.txt" << std::endl;
return 1;
}
// 像 cout 一样写入
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 | AppName=SalarySystem |
B. 读取文本文件 (使用 >> 和 getline)
方式一:使用 >> (按空格/换行分隔)
流提取运算符
>>会自动以空白字符(空格、制表符、换行符)为分隔符来读取数据。这正是你项目中
loadFromFile使用的方法:ifs >> type >> name >> data;
方式二:使用 std::getline() (按行读取)
getline读取一整行,直到遇到换行符。
实例 (结合两种方式):
1 |
|
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
// 假设有一个简单的数据结构 (POD - Plain Old Data)
struct Player {
int id;
char name[50]; // 二进制读写时,用 C 风格字符串更简单
double score;
};
int main() {
Player p1 = {101, "TestUser", 95.5};
Player p2_load; // 用于加载数据
// --- 1. 写入二进制文件 ---
// 必须指定 std::ios::binary
std::ofstream outFile("player.dat", std::ios::binary);
if (!outFile) {
std::cerr << "无法创建 player.dat" << std::endl;
return 1;
}
// 将 p1 对象的内存数据,原封不动地写入文件
outFile.write(reinterpret_cast<const char*>(&p1), sizeof(Player));
outFile.close();
std::cout << "二进制数据写入成功。" << std::endl;
// --- 2. 读取二进制文件 ---
std::ifstream inFile("player.dat", std::ios::binary);
if (!inFile) {
std::cerr << "无法读取 player.dat" << std::endl;
return 1;
}
// 从文件读取数据,原封不动地填充到 p2_load 对象的内存中
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;
}
1 |
|
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// 错误!不能对包含 std::string 的类使用二进制 write
Manager m("Alice");
outFile.write(reinterpret_cast<const char*>(&m), sizeof(Manager));结论: 对于你的项目,使用文本 I/O(即
<<和>>)是正确且安全的选择。
