核心要点速览
- 文本文件:
>>/<<、getline()读写,自动转换换行符,适配人类可读数据(日志、配置)。
- 二进制文件:
read()/write()操作原始字节流,适配非文本数据(图片、结构体),效率更高。
- 文件指针:
seekg()/seekp()定位、tellg()/tellp()获取位置,支持随机访问。
- 关键:模式区分、读写混用问题、字节对齐、缓冲区刷新、跨平台兼容核心点。
一、文本文件:读写逻辑与易错
文本文件以字符编码(ASCII/UTF-8)存储,读写时自动处理换行符转换(如 Windows 下\n→\r\n),核心关注 “读写一致性” 与 “缓冲区问题”。
1. 读写方法
(1)写入操作(ofstream)
1 2 3 4 5
| ofstream ofs("text.txt", ios::out | ios::app); if (!ofs) return -1; ofs << "姓名:Tom" << endl; ofs.put('!'); ofs.close();
|
(2)读取操作(ifstream)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ifstream ifs("text.txt"); if (!ifs) return -1;
string line; while (getline(ifs, line)) { cout << line << endl; }
string name; int age; ifs >> name >> age;
ifs.close();
|
2. 易错
(1)getline()与>>混用陷阱
>>读取后缓冲区残留换行符,导致getline()直接读空行:
1 2 3 4 5 6 7
| int num; string line; ifs >> num; ifs.ignore();
ifs.ignore(numeric_limits<streamsize>::max(), '\n'); getline(ifs, line);
|
(2)缓冲区未刷新导致数据丢失
1 2 3 4 5
| ofs << "重要数据" << '\n';
ofs << "重要数据" << endl;
|
(3)getline()与get()的换行差异
getline():读取时自动丢弃换行符,不残留;
get():读取单个字符(含换行),换行符会留在缓冲区,后续读取需注意。
二、二进制文件:原始字节操作与跨平台
二进制文件直接存储原始字节(无编码 / 换行转换),适合结构化数据存储,但需额外处理字节对齐、字节序、数据类型长度等差异才能实现跨平台兼容。
1. 读写方法
(1)二进制写入(ios::binary必加)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #pragma pack(1) struct Student { int32_t id; char name[20]; }; #pragma pack()
ofstream ofs("data.bin", ios::out | ios::binary); if (!ofs) return -1; Student s = {1, "Tom"};
ofs.write(reinterpret_cast<const char*>(&s), sizeof(s)); ofs.close();
|
(2)二进制读取
1 2 3 4 5 6 7 8 9
| 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);
- 后果:直接跨平台读写会导致数据解析错误(如小端写入的
100 大端读取为 1677721600);
- 解决方案:统一转为 “网络字节序”(大端)读写,用
htons()/htonl()(主机→网络)、ntohs()/ntohl()(网络→主机)转换:
1 2 3 4 5 6 7
| 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)获取文件大小
1 2 3 4
| ifstream ifs("file.txt", ios::binary); ifs.seekg(0, ios::end); int file_size = ifs.tellg(); ifs.seekg(0, ios::beg);
|
(2)随机读取指定字节
1 2 3
| ifs.seekg(100, ios::beg); char c; ifs.get(c);
|
四、问答
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')清除缓冲区残留字符(直到换行符为止)。