ofstream追加写必须显式指定std::ios::app标志,否则默认覆盖;该标志隐含输出语义,无需额外加std::ios::out;不支持线程安全,需外部加锁;文本模式下windows自动转换\n为\r\n,二进制数据须加std::ios::binary。

ofstream 构造时必须用 std::ios::app
不加这个标志,ofstream 默认是覆盖写入——哪怕文件已存在、你只写几字节,也会清空整个文件。这是最常踩的坑,尤其从 fstream 或 Python 的 "a" 模式转过来的人容易忽略。
正确写法是显式传入 std::ios::app 标志:
std::ofstream file("log.txt", std::ios::app);
注意:std::ios::out 不需要再写,因为 ofstream 本身就是输出流,std::ios::app 已隐含了输出语义;重复加反而可能干扰行为(某些旧编译器会报错)。
- 不能只开
std::ios::ate:它只是把指针移到末尾,但不保证追加——下次write()还是从头覆盖 - 不要用
std::ios::in | std::ios::out混合打开再 seek:逻辑复杂、易出错,且 Windows 下对纯文本文件支持不稳定 - 如果文件不存在,
std::ios::app会自动创建,无需提前判断
追加写入前别手动 seekp()
有人担心“指针不在末尾会不会写错”,于是加一行 file.seekp(0, std::ios::end)。这完全多余,std::ios::app 模式下每次 operator 或 <code>write() 都自动定位到当前文件末尾,强制 seek 反而可能破坏原子性(尤其多线程或并发写时)。
立即学习“C++免费学习笔记(深入)”;
- 即使中间有其他操作(比如
file.clear()),只要没关闭流,追加位置始终受app控制 - 唯一例外是调用了
file.tellp()后又做了非追加写(如write()到中间),那才需重新 seek —— 但这就不是“追加”场景了
多线程写同一个文件要加锁,std::ios::app 不保证线程安全
操作系统层面,O_APPEND 标志(Linux/Unix)或等效机制(Windows)能保证单次 write() 原子追加,但 C++ 流对象本身不是线程安全的: 是多步操作(格式化 + 写缓冲 + 刷新),多个线程同时调用会导致日志交错、换行错乱甚至崩溃。
- 简单方案:用
std::mutex包住整个写操作,包括和 <code> - 避免用
std::endl:它强制 flush,频繁刷盘影响性能;改用"\n",最后统一file.flush() - 更健壮的做法是用无锁日志库(如 spdlog),但若只写几行调试信息,一把锁足够
Windows 下换行符和文本模式要注意
在 Windows 上,用 std::ios::app 打开文件默认是文本模式,\n 会被自动转成 \r\n。这通常没问题,但如果你写的是二进制数据(比如序列化结构体),就必须加 std::ios::binary:
std::ofstream file("data.bin", std::ios::app | std::ios::binary);
漏掉 binary 会导致数据错位,尤其是写入 0x0A 字节时被替换成 0x0D 0x0A,读取时就对不上。
- 文本模式下
\n→\r\n是透明的,但会影响文件大小和校验值 - macOS/Linux 不受影响(换行即
\n),所以跨平台项目务必显式区分binary - 不要依赖
std::ofstream的默认构造行为,始终显式写出所有必要标志











