具体来说,我对 istream& getline ( istream& is, string& str );
感兴趣。 ifstream 构造函数是否有一个选项可以告诉它将所有换行符编码转换为 '\n' 在引擎盖下?我希望能够调用 getline
并让它优雅地处理所有行尾。
更新:澄清一下,我希望能够编写几乎可以在任何地方编译的代码,并且几乎可以从任何地方获取输入。包括带有 '\r' 而没有 '\n' 的罕见文件。最大限度地减少对软件的任何用户的不便。
解决这个问题很容易,但我仍然对在标准中灵活处理所有文本文件格式的正确方法感到好奇。
getline
将整行读取到字符串中,直到 '\n'。 '\n' 从流中消耗,但 getline 不将其包含在字符串中。到目前为止这很好,但是在字符串中包含的 '\n' 之前可能有一个 '\r'。
在文本文件中看到 three types of line endings:'\n' 是 Unix 机器上的常规结尾,'\r' (我认为)用于旧的 Mac 操作系统,Windows 使用一对,'\r' 后跟'\n'。
问题是 getline
将 '\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.
}
编辑感谢 Neil 指出 f.good()
不是我想要的。 !f.fail()
是我想要的。
我可以自己手动删除它(请参阅此问题的编辑),这对于 Windows 文本文件很容易。但我担心有人会输入一个只包含'\r'的文件。在那种情况下,我认为 getline 会消耗整个文件,认为它是一行!
..这甚至没有考虑Unicode :-)
..也许Boost有一种很好的方法来一次从任何文本文件类型中消耗一行?
编辑我正在使用它来处理 Windows 文件,但我仍然觉得我不应该这样做!这不会分叉 '\r'-only 文件。
if(!line.empty() && *line.rbegin() == '\r') {
line.erase( line.length()-1, 1);
}
f.good()
应该是 f.fail()
的反义词。
正如 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 std::streambuf::traits_type::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;
}
}
}
这是一个测试程序:
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++ 运行时应该正确处理适用于您的特定平台的任何 endline 约定。具体来说,此代码应适用于所有平台:
#include <string>
#include <iostream>
using namespace std;
int main() {
string line;
while( getline( cin, line ) ) {
cout << line << endl;
}
}
当然,如果您正在处理来自另一个平台的文件,那么所有的赌注都没有了。
由于两个最常见的平台(Linux 和 Windows)都以换行符终止行,Windows 在其前面带有回车符,因此您可以检查上述代码中 line
字符串的最后一个字符是否为\r
如果是这样,请在执行特定于应用程序的处理之前将其删除。
例如,您可以为自己提供一个看起来像这样的 getline 样式函数(未经测试,仅出于教学目的使用索引、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;
}
safegetline
是解决方案的重要组成部分。但是如果这个程序是在 Windows 上编译的,我还需要以二进制格式打开文件吗? Windows 编译器(在文本模式下)是否允许 '\n' 表现得像 '\r''\n'? ifstream f("f.txt", ios_base :: binary | ios_base::in );
您是在 BINARY 还是 TEXT 模式下读取文件?在 TEXT 模式下,一对回车/换行符 CRLF 被解释为 TEXT 行尾或行尾字符,但在 BINARY 中,您一次只能获取一个字节,这意味着必须忽略任何一个字符并将其留在要作为另一个字节获取的缓冲区!回车是指在打字机中,打印臂所在的打字车已经到达纸张的右边缘并返回到左边缘。这是一个非常机械的模型,机械打字机的模型。然后换行意味着纸卷向上旋转一点,这样纸就可以开始另一行打字了。据我所知,ASCII 中的低位数字之一意味着在不输入的情况下向右移动一个字符,死字符,当然 \b 意味着退格:将汽车向后移动一个字符。这样你就可以添加特殊效果,比如底层(输入下划线),删除线(输入减号),近似不同的重音,取消(输入X),而不需要扩展键盘,只需调整汽车沿线的位置输入换行符。因此,您可以使用字节大小的 ASCII 电压来自动控制打字机,而无需在其间使用计算机。引入自动打字机时,AUTOMATIC 表示一旦到达纸的最远边缘,小车返回左侧并应用换行,也就是说,假设随着纸卷向上移动,小车自动返回!所以你不需要两个控制字符,只需要一个,\n、换行符或换行符。
这与编程无关,但 ASCII 更老,嘿!看起来有些人在开始做文字的时候并没有思考! UNIX 平台采用电动自动打字机; Windows 模型更完整,可以控制机械机器,虽然有些控制字符在计算机中变得越来越没用,比如铃铛字符,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);
...
将是处理所有类型文件的最正确方法。但是请注意,TEXT 模式下的 \n 实际上是字节对 0x0d 0x0a,但 0x0d 只是 \r:\n 在 TEXT 模式下包含 \r 而不是 BINARY,因此 \n 和 \r\n 是等价的......或应该。这实际上是一个非常基本的行业混乱,典型的行业惯性,正如惯例所说的 CRLF,在所有平台上,然后陷入不同的二进制解释。严格来说,仅包含 0x0d(回车)作为 \n(CRLF 或换行)的文件在 TEXT 模式下格式错误(打字机:只需返回汽车并删除所有内容......),并且是非面向行的二进制格式(\r 或 \r\n 表示面向行),因此您不应该以文本形式阅读!该代码可能会因某些用户消息而失败。这不仅取决于操作系统,还取决于 C 库实现,增加了混乱和可能的变化......(特别是对于透明的 UNICODE 翻译层,为混乱的变化增加了另一个清晰度)。
前面的代码片段(机械打字机)的问题是,如果在\r(自动打字机文本)之后没有\n 字符的话,效率非常低。然后它还假定 C 库被迫忽略文本解释(语言环境)并放弃纯粹字节的 BINARY 模式。两种模式的实际文本字符应该没有区别,只有控制字符,所以一般来说读 BINARY 比 TEXT 模式好。此解决方案对于独立于 C 库变体的 BINARY 模式典型的 Windows OS 文本文件是有效的,而对于其他平台文本格式(包括 Web 翻译成文本)效率低下。如果您关心效率,则可以使用函数指针,以您喜欢的方式对 \r 与 \r\n 行控件进行测试,然后选择最佳的 getline 用户代码到指针中并从中调用它它。
顺便说一句,我记得我也发现了一些 \r\r\n 文本文件......它转换为双行文本,就像一些印刷文本消费者仍然需要的那样。
一种解决方案是首先搜索所有行结尾并将其替换为 '\n' - 就像 Git 默认情况下所做的那样。
除了编写自己的自定义处理程序或使用外部库之外,您很不走运。最简单的方法是检查以确保 line[line.length() - 1]
不是 '\r'。在 Linux 上,这是多余的,因为大多数行都会以 '\n' 结尾,这意味着如果它处于循环中,您将失去相当多的时间。在 Windows 上,这也是多余的。但是,以 '\r' 结尾的经典 Mac 文件呢? std::getline 不适用于 Linux 或 Windows 上的这些文件,因为 '\n' 和 '\r' '\n' 都以 '\n' 结尾,无需检查 '\r'。显然,这种与这些文件一起工作的任务不会很好地工作。当然,还有大量的 EBCDIC 系统,这是大多数图书馆不敢解决的问题。
检查 '\r' 可能是解决您的问题的最佳方法。以二进制模式读取将允许您检查所有三个常见的行尾('\r'、'\r\n' 和 '\n')。如果您只关心 Linux 和 Windows,因为旧式 Mac 行尾不应该存在很长时间,请仅检查 '\n' 并删除尾随的 '\r' 字符。
如果知道每行有多少项目/数字,则可以读取一行,例如 4 个数字
string num;
is >> num >> num >> num >> num;
这也适用于其他行尾。
不幸的是,接受的解决方案的行为与 std::getline()
不完全相同。要获得该行为(对我的测试),需要进行以下更改:
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 std::streambuf::traits_type::eof():
is.setstate(std::ios::eofbit); //
if(t.empty()) // <== change here
is.setstate(std::ios::failbit); //
return is;
default:
t += (char)c;
}
}
}
根据https://en.cppreference.com/w/cpp/string/basic_string/getline:
从输入中提取字符并将它们附加到 str 直到出现以下之一(按列出的顺序检查)输入的文件结束条件,在这种情况下,getline 设置 eofbit。下一个可用的输入字符是 delim,由 Traits::eq(c, delim) 测试,在这种情况下,分隔符从输入中提取,但不附加到 str。 str.max_size() 字符已被存储,在这种情况下 getline 设置失败位并返回。如果由于某种原因没有提取字符(甚至没有被丢弃的分隔符),getline 设置失败位并返回。
t
是否为空的目的是什么。无论是否读入了其他字符,不应该设置该位吗?std::get_line
的行为,它忽略了一个空的最后一行。我在 eof 案例中使用了以下代码来模拟std::get_line
行为:is.setstate(std::ios::eofbit); if (t.empty()) is.setstate(std::ios::badbit); return is;