核心要点速览
- 文本文件:
>>/<<、getline()读写,自动转换换行符,适配人类可读数据(日志、配置)。 - 二进制文件:
read()/write()操作原始字节流,适配非文本数据(图片、结构体),效率更高。 - 文件指针:
seekg()/seekp()定位、tellg()/tellp()获取位置,支持随机访问。 - 面试关键:模式区分、读写混用问题、字节对齐、缓冲区刷新、跨平台兼容核心点。
一、文本文件:读写逻辑与易错
文本文件以字符编码(ASCII/UTF-8)存储,读写时自动处理换行符转换(如 Windows 下\n→\r\n),核心关注 "读写一致性" 与 "缓冲区问题"。
1. 读写方法
(1)写入操作(ofstream)
cpp
ofstream ofs("text.txt", ios::out | ios::app); // 追加模式
if (!ofs) return -1; // 检查打开失败
ofs << "姓名:Tom" << endl; // endl=换行+刷新缓冲区
ofs.put('!'); // 写入单个字符
ofs.close(); // 显式关闭,确保数据落盘
(2)读取操作(ifstream)
cpp
ifstream ifs("text.txt");
if (!ifs) return -1;
// 方式1:getline()读整行(保留空格,丢弃换行符)
string line;
while (getline(ifs, line)) {
cout << line << endl;
}
// 方式2:>>按空白分割读(跳过空格/换行)
string name;
int age;
ifs >> name >> age;
ifs.close();
2. 易错
(1)getline()与>>混用陷阱
>>读取后缓冲区残留换行符,导致getline()直接读空行:
cpp
int num;
string line;
ifs >> num; // 读入数字后,缓冲区剩"\n"
ifs.ignore(); // 清除残留换行符(简单场景)
// 通用解法:清除到换行符为止,避免多字符残留
ifs.ignore(numeric_limits<streamsize>::max(), '\n');
getline(ifs, line); // 正常读取下一行
(2)缓冲区未刷新导致数据丢失
cpp
// 错误:仅换行不刷新,程序异常退出时数据可能丢失
ofs << "重要数据" << '\n';
// 正确:用endl刷新或手动调用flush()
ofs << "重要数据" << endl;
// ofs.flush(); // 不换行,仅刷新
(3)getline()与get()的换行差异
getline():读取时自动丢弃换行符,不残留;get():读取单个字符(含换行),换行符会留在缓冲区,后续读取需注意。
二、二进制文件:原始字节操作与跨平台
二进制文件直接存储原始字节(无编码 / 换行转换),适合结构化数据存储,但需额外处理字节对齐、字节序、数据类型长度等差异才能实现跨平台兼容。
1. 读写方法
(1)二进制写入(ios::binary必加)
cpp
// 关闭字节对齐(跨平台读取关键)
#pragma pack(1)
struct Student {
int32_t id; // 用固定长度类型(int32_t),避免平台长度差异
char name[20]; // 用char数组,避免string动态内存问题
};
#pragma pack()
ofstream ofs("data.bin", ios::out | ios::binary);
if (!ofs) return -1;
Student s = {1, "Tom"};
// 强转数据地址为char*,写入整个结构体
ofs.write(reinterpret_cast<const char*>(&s), sizeof(s));
ofs.close();
(2)二进制读取
cpp
ifstream ifs("data.bin", ios::in | ios::binary);
if (!ifs) return -1;
Student s;
ifs.read(reinterpret_cast<char*>(&s), sizeof(s));
// 检查读取成功
if (ifs.good()) {
cout << "ID: " << s.id << ", Name: " << s.name << endl;
}
ifs.close();
2. 跨平台
(1)字节对齐问题
- 原因:不同编译器默认对齐规则不同(如 GCC 与 MSVC),结构体可能被填充空白字节;
- 后果:跨平台读取时字节错位(如结构体总长度不一致);
- 解决方案:用
#pragma pack(1)设置 1 字节对齐(紧凑模式),读写两端需保持一致。
(2)字节序(大小端)问题
- 原因:多字节数据(
int32_t、long等)在不同平台的字节排列顺序不同:- 小端(x86 平台):低位字节存低地址(如
100存为0x64 0x00 0x00 0x00); - 大端(部分 ARM 平台、网络字节序):高位字节存低地址(如
100存为0x00 0x00 0x00 0x64);
- 小端(x86 平台):低位字节存低地址(如
- 后果:直接跨平台读写会导致数据解析错误(如小端写入的
100大端读取为1677721600); - 解决方案:统一转为 "网络字节序"(大端)读写,用
htons()/htonl()(主机→网络)、ntohs()/ntohl()(网络→主机)转换:
cpp
// 写入时:主机字节序 → 网络字节序(大端)
s.id = htonl(s.id);
ofs.write(reinterpret_cast<const char*>(&s), sizeof(s));
// 读取时:网络字节序 → 主机字节序
ifs.read(reinterpret_cast<char*>(&s), sizeof(s));
s.id = ntohl(s.id);
(3)数据类型长度问题
- 原因:不同平台基础类型长度可能不同(如 32 位系统
long是 4 字节,64 位系统是 8 字节); - 后果:
sizeof(long)不一致导致读写字节数不匹配,数据错位; - 解决方案:用 C++11 固定长度类型(
int32_t/uint32_t/int64_t),避免使用int/long等平台相关类型。
三、文件指针:随机访问与经典场景
1. 主要函数
| 函数 | 功能 | 适用场景 |
|---|---|---|
tellg() |
获取读指针当前位置(字节偏移量) | 记录读取位置、计算文件大小 |
seekg(pos, mode) |
移动读指针,mode:ios::beg(开头)/ios::cur(当前)/ios::end(结尾) |
定位读取位置 |
tellp() |
获取写指针当前位置 | 记录写入位置 |
seekp(pos, mode) |
移动写指针(参数同seekg) |
定位写入位置 |
2. 常见场景
(1)获取文件大小
cpp
ifstream ifs("file.txt", ios::binary); // 二进制模式避免换行符影响
ifs.seekg(0, ios::end); // 指针移至文件尾
int file_size = ifs.tellg(); // 尾位置 = 文件字节数
ifs.seekg(0, ios::beg); // 指针回退至开头,便于后续读取
(2)随机读取指定字节
cpp
ifs.seekg(100, ios::beg); // 从文件开头偏移100字节
char c;
ifs.get(c); // 读取第101个字节(偏移从0开始)
四、问答
1. 文本文件和二进制文件的区别?
- 存储:文本存字符编码(人类可读),二进制存原始字节(机器可读);
- 转换:文本模式自动转换换行符 / 编码,二进制模式无任何转换;
- 效率:二进制 > 文本;
- 场景:文本用配置 / 日志,二进制用图片 / 结构体 / 大文件。
2. 二进制读写结构体实现跨平台,需处理哪些问题?
需解决 3 个主要问题,避免数据错位:
- 字节对齐:用
#pragma pack(1)统一 1 字节对齐; - 字节序:用
htons()/htonl()转换为网络字节序(大端); - 数据类型长度:用固定长度类型(
int32_t),替代int/long。
3. fstream中seekg()和seekp()需要分别调用吗?
默认情况下,fstream的读写指针是关联的(移动一个会同步影响另一个),但部分编译器实现可能分离;保险起见,若需独立控制读写位置(如边读边写不同区域),建议分别调用。
4. getline()与>>混用的问题及解决方案?
- 问题:
>>读取后缓冲区残留换行符,导致getline()读空行; - 解决方案:用
ifs.ignore(numeric_limits<streamsize>::max(), '\n')清除缓冲区残留字符(直到换行符为止)。