让 std :: ifstream 处理 LF、CR 和 CRLF?

新手上路,请多包涵

具体来说,我对 istream& getline ( istream& is, string& str ); 感兴趣。 ifstream 构造函数是否有一个选项可以告诉它将所有换行符编码转换为 ‘\n’ 在引擎盖下?我希望能够调用 getline 并让它优雅地处理所有行尾。

更新:为了澄清,我希望能够编写几乎可以在任何地方编译的代码,并且几乎可以从任何地方获取输入。包括带有 ‘\r’ 而没有 ‘\n’ 的罕见文件。最大限度地减少对软件的任何用户的不便。

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

getline 将整行读取到一个字符串中,直到 ‘\n’。 ‘\n’ 从流中消耗,但 getline 不将其包含在字符串中。到目前为止这很好,但是在字符串中包含的 ‘\n’ 之前可能有一个 ‘\r’。

在文本文件中可以看到 三种类型的行尾:’\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);
}

原文由 Aaron McDaid 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
2 个回答

正如 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;
}

原文由 Johan Råde 发布,翻译遵循 CC BY-SA 3.0 许可协议

不幸的是,接受的解决方案的行为与 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

  1. 从输入中提取字符并将它们附加到 str 直到发生以下情况之一(按列出的顺序检查)

    1. 输入的文件结束条件,在这种情况下,getline 设置 eofbit。
    2. 下一个可用的输入字符是 delim,由 Traits::eq(c, delim) 测试,在这种情况下,分隔符从输入中提取,但不附加到 str。
    3. str.max_size() 字符已被存储,在这种情况下 getline 设置失败位并返回。
  2. 如果由于某种原因没有提取字符(甚至没有被丢弃的分隔符),getline 设置失败位 并返回。

原文由 Gergely Nagy 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题