繁体   English   中英

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

[英]Getting std :: ifstream to handle LF, CR, and CRLF?

具体来说,我对istream& getline ( istream& is, string& str );感兴趣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.
}

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

我可以自己手动删除它(请参阅此问题的编辑),这对于 Windows 文本文件来说很容易。 但我担心有人会输入一个只包含 '\\r' 的文件。 在这种情况下,我认为 getline 会消耗整个文件,认为它是一行!

.. 这甚至没有考虑 Unicode :-)

.. 也许 Boost 有一种很好的方法可以从任何文本文件类型中一次使用一行?

编辑我正在使用它来处理 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 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++ 运行时应该正确处理特定平台的任何结束线约定。 具体来说,此代码应适用于所有平台:

#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;
}

您是在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);
...

将是处理所有类型文件的最正确方法。 然而要注意\\ n在文本模式实际上是字节对0X0D 0X0A,但0X0D只是\\ R:\\ n包括在文本模式\\ r而不是在BINARY,所以\\ n和\\ r \\ n为相当于...或应该。 这实际上是一个非常基本的行业混乱,典型的行业惯性,按照惯例,在所有平台上都讲CRLF,然后陷入不同的二进制解释。 严格来说,文件包含 0x0d(回车)作为 \\n(CRLF 或换行),在文本模式下格式不正确(打字机:只需返回汽车并删除所有内容......),并且是非行导向的二进制格式(\\r 或 \\r\\n 表示面向行),因此您不应将其作为文本阅读! 代码应该会失败,可能会出现一些用户消息。 这不仅取决于操作系统,还取决于 C 库实现,增加了混乱和可能的变化......(特别是对于透明的 UNICODE 翻译层,为混乱的变化添加了另一个表达点)。

前面的代码片段(机械打字机)的问题在于,如果 \\r(自动打字机文本)后面没有 \\n 字符,则效率非常低。 然后它还假定BINARY模式,其中 C 库被迫忽略文本解释(语言环境)并放弃纯粹的字节。 两种模式的实际文本字符应该没有区别,只有控制字符,所以一般来说读BINARYTEXT模式好。 此解决方案对于独立于 C 库变体的BINARY模式典型 Windows 操作系统文本文件是有效的,而对于其他平台文本格式(包括 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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM