可携式换行符(newline)

15
这是一个不愉快的惊喜,发现在Windows上'\n'被替换成了"\r\n",我之前并不知道。(我猜Mac上也会被替换...) 有没有一种简单的方法可以确保Linux、Mac和Windows用户可以轻松地交换文本文件? 所谓简单的方法是指:不用以二进制模式写入文件或自己(或使用第三方程序/代码)测试和替换行尾字符。这个问题影响了我做文本文件I/O的C++程序。

2
@AtesGoral 这些与执行文本输入/输出的可执行文件无关。 - Ali
4
“不以二进制模式写入文件”,这是“简单”的方法,为什么你想避免使用它? - CB Bailey
抱歉,但换行符并没有被“秘密”替换。这种行为已经有很好的文档记录了。来自文件在线教程:“非二进制文件被称为文本文件,由于某些特殊字符(如换行符和回车符)的格式化,可能会发生一些翻译。” - André Caron
@CharlesBailey 我不知道你可以在二进制模式下使用operator<< :) 我只用过write在二进制模式下。我预计读取时会出现问题,但看起来它运行良好。仍在测试中... - Ali
2
@CharlesBailey 事实证明,二进制模式是解决方案。这是我的知识不足所致... - Ali
显示剩余2条评论
3个回答

15

抱歉部分重复其他答案,但为了完整起见:

谬论: endl 是“更便携”的,因为它会根据平台惯例写入换行符。

真相: endl 被定义为向流中写入 \n,然后调用 flush。所以实际上你几乎不想使用它。所有写入文本模式流的 \n 都会被 CRT 在后台隐式转换成 \r\n,无论你使用 os<<endlos<<'\n' 还是 fputs("\n",file)

谬论:你应该在文本模式下打开文件来写文本,在二进制模式下打开文件来写二进制数据。

真相: 文本模式之所以存在,是因为以前有一些文件系统区分文本文件和二进制文件。在我知道的任何健全的平台上,它已经不再成立了。你同样可以将文本写入以二进制方式打开的文件中,只是在 Windows 上你会失去自动的 \n\r\n 的转换。然而,这种转换会带来更多的麻烦。其中之一是它使你的代码在不同的平台上表现不同,并且 tell/seek 变得难以使用。因此最好避免这种自动转换。请注意,POSIX 不区分二进制模式和文本模式。

如何进行文本处理:用二进制模式打开所有内容,仅使用普通的 \n 即可。您还需要关注编码。为了正确处理 Unicode,请统一使用 UTF-8。内部使用 UTF-8 编码的窄字符串 ,而不是在不同平台上有所不同的 wchar_t。您的代码将变得更容易移植。

提示:你可以通过以下方式强制 MSVC 默认以二进制模式打开所有文件。应该按照以下步骤操作:

#include <stdio.h>
#include <iostream>
int main() {
    _fmode = _O_BINARY;
    std::ofstream f("a.txt"); // opens in binary mode
}

编辑:自2021年起,Windows 10的记事本支持UNIX换行符。


1
@LokiAstari:我并不是在推崇fopen,它只是最简单和最明确的例子。你可能会更喜欢编辑过的版本。 - Yakov Galka
2
@ybungalobill:在二进制模式下使用 '\n' 会产生 Unix 行尾符。在 Windows 上,这会破坏像记事本这样的烂文本编辑器以及你将这些内容粘贴到的几乎所有文本框(即使是从处理 Unix 行尾符的编辑器中复制的)。这真的是你所倡导的吗?还是我完全误解了你的意思? - Marcelo Cantos
1
@MarceloCantos: 记事本只是一个文本编辑器的借口。当复制并粘贴时,一些编辑器会将 '\n' 转换为 '\r\n' (例如 Wordpad 或我检查过的 Web 浏览器),尽管我相信接收方应该理解 '\n'。话虽如此,我承认,如果文本文件面向非技术终端用户,这个指南是不可接受的,因为她不会关心你的程序是否“正确”。 - Yakov Galka
1
@ybungalobill:这不是关于非技术用户的问题。我不知道有哪个运行在Windows上的文本编辑器遵循您所倡导的策略。即使是emacs和vim默认也会发出CRLF。无论是否合理,Windows确实区分文本和二进制文件,忽视这一点只会让问题更严重。请注意,我并不反对您的建议作为这个问题的答案,因为它涉及跨平台可移植性。我担心的是我感到您主张在所有情况下使用二进制I/O。如果这不是您的意图,那么我为得出错误的结论而道歉。 - Marcelo Cantos
@ybungalobill:并非所有库调用都会忽略空格。fgets() 会将 CR 读入缓冲区,这会导致取决于输入文件中行尾的不同行为。是否重要取决于程序员的意图;它不应受到您建议的不可谈判规则的约束。关于我的“声明”:大多数 C 程序(特别是可移植的程序)使用 fopen(),在 Windows 上会对文本和二进制文件进行不同的处理(至少在我使用的每个运行时库上都是如此)。 - Marcelo Cantos
显示剩余2条评论

12

问题并不在于endl,而是文本流根据系统标准重新格式化换行符。

如果您不想这样做,只需不使用文本流-使用二进制流。也就是说,用ios::binary标志打开您的文件。

话虽如此,如果唯一的问题是用户可以交换文件,我将不会再费心考虑输出模式,相反,我会确保您的程序能够在不崩溃的情况下接受不同的格式,也就是说,它应该接受不同的行尾。

顺便说一句,任何像样的文本编辑器都会这样做(但是Windows上的默认notepad.exe不是一个像样的文本编辑器,并且无法正确处理Unix行尾)。


7

如果你真的只想要一个ASCII换行符,最简单的方法是以二进制模式打开文件:在非二进制模式下,\n会被替换为特定平台的行尾序列(例如,它可能被替换为LF/CR或CR/LF序列;在Unix系统上通常只是LF)。在二进制模式下,不会进行这种替换。关闭替换也是二进制模式唯一的作用。

顺便说一句,使用endl等价于写入一个\n并刷新流。常常意外的刷新可能会导致严重的性能问题。因此,应该很少使用endl,并且仅在需要刷新时使用。


我只想说,在文本模式下,'\n'会被替换为特定于平台的ELS(行尾序列)。 - Martin York

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接