二进制文件读写必须显式指定std::ios::binary标志,write/read操作原始字节块,n为字节数;pod结构体可直接读写但需注意内存对齐与字节序;流状态应通过read返回值判断而非eof()。

用 std::ifstream 和 std::ofstream 以二进制模式打开文件
关键不是函数名,而是打开时的标志位。默认的文本模式会做换行符转换(比如 Windows 的 \r\n → \n),二进制读写必须显式指定 std::ios::binary,否则数据会被悄悄篡改。
常见错误:只写 std::ofstream f("a.bin") —— 这是文本模式,哪怕你后面用 write() 写 char*,在 Windows 下也可能出问题。
正确写法:
std::ofstream ofs("data.bin", std::ios::binary);
std::ifstream ifs("data.bin", std::ios::binary);
注意:std::ios::binary 必须和 std::ios::out / std::ios::in 同时传入;单独写 std::ios::binary 不生效。
立即学习“C++免费学习笔记(深入)”;
write() 和 read() 的参数含义与常见误用
write() 和 read() 是流对象的成员函数,不是全局函数。它们操作的是原始字节块,不识别类型、不处理序列化,只管“从哪来、写多少”或“往哪存、读多少”。
两个函数签名一致:
basic_ostream& write(const char* s, std::streamsize n); basic_istream& read(char* s, std::streamsize n);
容易踩的坑:
-
n是字节数,不是元素个数 —— 写一个int arr[10],要传sizeof(arr)或10 * sizeof(int),不能只传10 - 目标缓冲区(
s)必须已分配且足够大,read()不检查越界 - 读写前建议用
ifs.gcount()检查上次read()实际读了多少字节(尤其配合readsome()时) - 写完后记得
ofs.flush()或ofs.close(),否则可能缓存未落盘
读写结构体或 POD 类型的注意事项
直接 write() 一个结构体指针是可行的,但仅限于纯 POD(Plain Old Data)类型 —— 即不含虚函数、不含非静态成员函数、不含引用、不含非 POD 成员、所有成员内存布局连续。
例如:
struct Header {
uint32_t magic;
uint16_t version;
uint8_t flags;
}; // ✅ 可直接 write(&h, sizeof(h))
<p>struct Bad {
std::string name; // ❌ 含指针,只写栈上对象本身,不包含字符串内容
virtual void f() {} // ❌ 有虚函数表指针
};
实际使用中要注意:
- 结构体最好用
#pragma pack(1)避免编译器填充(否则不同平台读出来字段错位) - 跨平台传输时,手动处理字节序(如用
htons()/ntohl()),别依赖本地uint32_t的高低字节顺序 - 写完立即用
ofs.tellp()记录位置,便于后续随机访问
错误检测与流状态判断不能只靠 eof()
很多人用 while (!ifs.eof()) { ifs.read(...); },这是典型错误 —— eof() 只在尝试读取失败后才置位,循环体里已经用了一次无效数据。
正确方式是判断 read() 返回值本身(流对象)是否仍为真:
int x;
while (ifs.read(reinterpret_cast<char*>(&x), sizeof(x))) {
// 成功读取了一个 int
}
// 退出时,可能是 eof,也可能是 read 失败(如磁盘错误)
if (ifs.bad()) {
// 硬件/IO 错误,不可恢复
} else if (ifs.fail() && !ifs.eof()) {
// 格式或长度错误(比如只剩 2 字节却想读 4 字节)
}
另外,gcount() 能告诉你上一次 read() 到底读了几字节,比盲目假设更可靠。
二进制 I/O 的核心就三点:开对模式、算对字节数、查对流状态。结构体序列化看着简单,但 padding、endianness、lifetime 任何一个没控住,读出来就是乱码或崩溃。别信“写进去就能读出来”,先用 xxd 或十六进制编辑器看一眼文件内容,比调十次试错更快。











