必须显式指定std::ios::binary标志读写二进制文件,否则换行符转换会破坏原始字节;应使用read()/write()而非>>/<<操作符;注意结构体POD性、字节序、类型大小及EOF正确判断。

用 std::ifstream 和 std::ofstream 以二进制模式打开文件
默认的文本模式会做换行符转换(比如 \r\n → \n),读写原始字节必须显式指定 std::ios::binary。漏掉这个标志,二进制数据大概率被悄悄破坏,尤其是含 0x0A、0x0D 的块。
- 打开读:
std::ifstream fin("data.bin", std::ios::binary); - 打开写:
std::ofstream fout("out.bin", std::ios::binary); - 追加写:加
std::ios::app,但注意它隐含从末尾开始,不保证覆盖 —— 二进制场景下更推荐先seekp()定位 - 如果文件路径含中文或特殊字符,Windows 下需用
std::filesystem::u8path()转 UTF-8 字符串传给构造函数,否则可能打不开
读写结构体或原始内存块时,别直接 >
operator 和 <code>operator>> 是为格式化文本设计的,对二进制完全无效 —— 它们会尝试解析/输出可读字符,甚至跳过空白,导致数据错位或截断。
- 正确做法是用
read()和write()成员函数 - 写一个结构体:
fout.write(reinterpret_cast<const char>(&obj), sizeof(obj));</const> - 读一个结构体:
fin.read(reinterpret_cast<char>(&obj), sizeof(obj));</char> - 务必检查
gcount()返回值,确认实际读取字节数是否等于预期(比如文件提前结束或磁盘错误) - 结构体含指针、虚函数表或非 POD 类型(如
std::string)时,不能直接读写 —— 会把指针地址存进去,下次读就是野指针
跨平台读写整数要注意字节序和类型大小
同一个 int 在 x86 和 ARM 上可能是小端或大端;sizeof(int) 在不同编译器/平台也可能不同(16/32/64 位)。直接读写原生类型,换机器就乱码。
- 固定宽度类型更安全:
int32_t、uint64_t(需#include <cstdint>) - 网络字节序(大端)是通用方案:
htons()/ntohs()处理 16 位,htonl()/ntohl()处理 32 位;64 位需手写转换或用bswap_64()(GCC/Clang) - 写入前统一转成网络序,读出后转回主机序 —— 这步漏掉,Linux 和 Windows 之间传文件必出问题
- 如果只是本机读写且不关心移植性,可以用
static_assert(std::is_standard_layout_v<T>)防止误用非 POD 类型
读到 EOF 后继续 read() 不报错,但 failbit 会被置位
很多人以为 fin.read() 返回 false 就代表出错,其实不是:它只表示流状态异常(比如磁盘故障),而“读到末尾”是合法状态,read() 仍返回 *this,但后续 gcount() 为 0,且 eofbit 置位 —— failbit 反而没动。
立即学习“C++免费学习笔记(深入)”;
- 判断是否真正读满:先
read(buf, n),再立刻检查fin.gcount() == n - 不要用
while (!fin.eof())循环 —— 它会在最后一次成功读取后多跑一次,导致处理脏数据 - 更健壮的循环写法:
while (fin.read(buf, size).gcount() == size) { /* 处理 */ } - 如果读取中途断电或文件被截断,
gcount()小于请求长度,这时要按实际字节数处理,而不是硬塞满缓冲区
二进制文件操作最麻烦的从来不是语法,而是边界条件:字节序混用、结构体内存布局假设、EOF 判断误用、以及把文本思维套在二进制上 —— 这些地方一松懈,数据就 silently 损坏。











