获取std :: ifstream来处理LF,CR和CRLF?

具体来说,我对istream& getline ( istream& is, string& str );感兴趣istream& getline ( istream& is, string& str ); 。 是否有ifstream构造函数的选项来告诉它将所有换行符编码转换为“\ n”? 我希望能够调用getline ,并优雅地处理所有的行结束。

更新 :为了澄清,我希望能够编写几乎在任何地方编译的代码,并将从几乎任何地方接受input。 包括具有“\ r”但不包含“\ n”的罕见文件。 最大限度地减less软件用户的不便。

解决这个问题很容易,但是我仍然对标准中的正确方式感到好奇,以灵活地处理所有文本文件格式。

getline将整行读入一个'\ n',并转换成一个string。 '\ n'从stream中消耗,但getline不包含在string中。 到目前为止,这很好,但是在\ n之前可能会有一个'\ r'被包含到string中。

在文本文件中有三种types的结尾符号 :'\ n'是Unix机器上的传统结尾,''r'是我以前在Mac操作系统上使用的,Windows使用'\ r'接着'\ n'。

问题在于getline在string的末尾留下'\ r'。

 ifstream f("a_text_file_of_unknown_origin"); string line; getline(f, line); if(!f.fail()) { // a non-empty line was read // BUT, there might be an '\r' at the end now. } 

编辑感谢尼尔指出, f.good()是不是我想要的。 !f.fail()是我想要的。

我可以自己手动删除它(请参阅此问题的编辑),这对Windows文本文件很容易。 但是我担心有人会提供一个只包含“\ r”的文件。 在这种情况下,我认为getline会占用整个文件,认为它是一个单一的行!

..这甚至不考虑Unicode 🙂

..也许升压有一个很好的方式来从任何文本文件types一次消耗一行?

编辑我正在使用这个,来处理Windows文件,但我仍然觉得我不应该! 而这不会为'\ r'-only文件分叉。

 if(!line.empty() && *line.rbegin() == '\r') { line.erase( line.length()-1, 1); } 

正如Neil指出的那样,“C ++运行时应该正确地处理任何行结束约定为您的特定的平台”。

但是,人们在不同的平台之间移动文本文件,所以不够好。 这是一个处理所有三行结尾(“\ r”,“\ n”和“\ r \ n”)的函数:

 std::istream& safeGetline(std::istream& is, std::string& t) { t.clear(); // The characters in the stream are read one-by-one using a std::streambuf. // That is faster than reading them one-by-one using the std::istream. // Code that uses streambuf this way must be guarded by a sentry object. // The sentry object performs various tasks, // such as thread synchronization and updating the stream state. std::istream::sentry se(is, true); std::streambuf* sb = is.rdbuf(); for(;;) { int c = sb->sbumpc(); switch (c) { case '\n': return is; case '\r': if(sb->sgetc() == '\n') sb->sbumpc(); return is; case EOF: // Also handle the case when the last line has no line ending if(t.empty()) is.setstate(std::ios::eofbit); return is; default: t += (char)c; } } } 

这里是一个testing程序:

 int main() { std::string path = ... // insert path to test file here std::ifstream ifs(path.c_str()); if(!ifs) { std::cout << "Failed to open the file." << std::endl; return EXIT_FAILURE; } int n = 0; std::string t; while(!safeGetline(ifs, t).eof()) ++n; std::cout << "The file contains " << n << " lines." << std::endl; return EXIT_SUCCESS; } 

C ++运行时应该正确地处理你的特定平台的任何末端约定。 具体来说,这个代码应该在所有平台上工作:

 #include <string> #include <iostream> using namespace std; int main() { string line; while( getline( cin, line ) ) { cout << line << endl; } } 

当然,如果您正在处理来自其他平台的文件,则所有投注都将closures。

由于两个最常见的平台(Linux和Windows)都使用换行符来终止行,而Windows在其前面带有回车符,因此可以在上面的代码中检查linestring的最后一个字符,以查看它是否为\r如果是这样,请在执行特定于应用程序的处理之前删除它。

例如,你可以为自己提供一个看起来像这样的getline样式函数(没有经过testing,使用索引,substr等仅用于教学目的):

 ostream & safegetline( ostream & os, string & line ) { string myline; if ( getline( os, myline ) ) { if ( myline.size() && myline[myline.size()-1] == '\r' ) { line = myline.substr( 0, myline.size() - 1 ); } else { line = myline; } } return os; } 

你正在以BINARYTEXT模式读取文件吗? 在TEXT模式下,双回车/换行符CRLF被解释为TEXT行尾或行尾字符,但是在BINARY中 ,一次只能读取一个字节,这意味着任何一个字符都必须被忽略并留在缓冲区被取作另一个字节! 在打字机中,回车装置意味着打印臂所在的打字机车已经到达纸张的右边缘并返回到左边缘。 这是机械打字机的机械模型。 然后换行意味着纸卷稍微旋转一点,以便纸张能够开始另一行打字。 就像我记得的那样,在ASCII码中的一个低位数意​​味着移动到正确的一个字符而不打字,死的字符,当然\ b意味着退格:将汽车移回一个字符。 这样,您可以添加特殊效果,如底层(types下划线),删除线(types减号),近似不同的重音符号,取消输出(typesX),而不需要扩展键盘,只需调整汽车的位置沿线input换行符。 所以你可以使用字节大小的ASCII电压来自动控制一台打字机,而无需使用计算机。 当引入自动打字机时, AUTOMATIC(自动)表示一旦到达纸张的最远边缘,汽车就会返回到左侧并且进行换行,也就是说,当滚筒向上移动时,汽车会自动返回! 所以你不需要两个控制字符,只有一个,\ n,新行或换行符。

这与编程没有任何关系,但ASCII更老,嘿! 看起来有些人在开始做文本的时候没有想到! UNIX平台采用电动自动型机器; 虽然有些控制字符在计算机上变得越来越有用,比如铃声字符,如果我记得的话,0x07 …一些被遗忘的文本一定是最初被控制字符捕获的对于电控打字机,它延续了这个模型。

实际上正确的变化是只包括\ r,换行符,回车是不必要的,也就是自动的,因此:

 char c; ifstream is; is.open("",ios::binary); ... is.getline(buffer, bufsize, '\r'); //ignore following \n or restore the buffer data if ((c=is.get())!='\n') is.rdbuf()->sputbackc(c); ... 

将是处理所有types的文件最正确的方式。 但是,在TEXT模式下\ n实际上是字节对0x0d 0x0a,但0x0d IS只是\ r:\ n在TEXT模式下包含\ r,但不在BINARY中 ,所以\ n和\ r \ n是等同的…或应该。 这实际上是一个非常基础的行业混乱,典型的行业惯性,因为在所有平台上,惯例是说CRLF,然后分解成不同的二进制解释。 严格来说,包括ONLY 0x0d(回车)作为\ n(CRLF或换行符)的文件在TEXT模式(打字机:只是返回汽车和删除所有内容…)中格式不正确,并且是非行二进制格式(\ r或\ r \ n表示面向行),所以你不应该阅读文本! 代码应该可能与某些用户消息失败。 这不仅取决于操作系统,而且还取决于C库的实现,增加了混淆和可能的变化…(特别是对于透明的UNICODE翻译层,为混淆变体添加了另一个连接点)。

前面的代码片段(机械打字机)的问题是,如果在\ r(自动打字机文本)之后没有\ n字符,效率非常低。 然后它也假定BINARY模式,C库被迫忽略文本解释(locale)并放弃纯粹的字节。 两种模式之间的实际文本字符应该没有区别,只有在控制字符中,所以一般来说读取BINARYTEXT模式要好。 该解决scheme对于BINARY模式(典型的Windows操作系统文本文件)而言是高效的, 而不依赖于C库变体,对其他平台文本格式(包括网页翻译成文本)效率低下。 如果你关心效率,要走的路是使用一个函数指针,不pipe你喜欢的方式来testing\ r \ r \ n行控件,然后select最好的getline用户代码到指针中,并从它。

顺便提一句,我记得我也发现了一些\ r \ r \ n文本文件…它翻译成双行文本,就像某些印刷文本消费者仍然需要的一样。

除了编写你自己的自定义处理程序或使用外部库,你是不走运的。 最简单的事情是检查确保line[line.length() - 1]不是'\ r'。 在Linux上,这是多余的,因为大多数行会以'\ n'结尾,这意味着如果这个循环处于循环状态,则会丢失相当长的一段时间。 在Windows上,这也是多余的。 然而,以'\ r'结尾的经典Mac文件呢? 因为'\ n'和'\ r''\ n'都以'\ n'结尾,因此std :: getline不适用于Linux或Windows上的文件,因此不需要检查'\ r'。 很明显,这些与这些文件一起工作的任务将不能正常工作。 当然,那么EBCDIC系统也是很多的,大多数的图书馆都不敢去对付。

检查“\ r”可能是解决您的问题的最佳方法。 以二进制模式读取将允许您检查所有三个公共行尾('\ r','\ r \ n'和'\ n')。 如果你只关心Linux和Windows,因为老式的Mac系列的结局应该不会太长,那么只检查'\ n'并删除结尾的'\ r'字符。

一种解决scheme是首先search并replace所有行结束符'\ n' – 就像例如默认的Git一样。