当输入的行结束符混合时,std::getline的替代方案

7
我正在尝试从std :: istream中读取行,但输入可能包含'\r'和/或'\n',因此std :: getline 无法使用。
抱歉大声喊出来,但这似乎需要强调......
输入可能包含换行符类型或两者都包含。
有没有标准的方法来解决这个问题?目前我正在尝试。
char c;
while (in >> c && '\n' != c && '\r' != c)
    out .push_back (c);

...但这会跳过空格。天啊!std :: noskipws - 需要进行更多的调整,现在它的行为不当。

肯定有更好的方法吧?!?


分隔符是混合在单个文件中,还是在文件之间会有所不同? - jonsca
我知道你可能可以在一次扫描中完成它,但我会进行两次扫描,第一次将所有行尾(CR,LF,CRLF)更改为std::endl(使用in.get()读取字符而非提取运算符),然后在第二次扫描中使用getline - jonsca
2个回答

4

通常读取一行的方法是使用 std::getline

注:如果您的 std::getline 实现有问题,您可以自己编写类似的代码,例如:

std::istream &getline(std::istream &is, std::string &s) { 
    char ch;

    s.clear();

    while (is.get(ch) && ch != '\n' && ch != '\r')
        s += ch;
    return is;
}

我需要补充一点的是,技术上来说这可能不是 std::getline 的问题,而是基础流实现的问题 -- 流需要将表示行结束的任何字符转化为平台上的换行符。无论哪个部分出了问题,如果你的实现有问题,那么这种方法可能会有所帮助(然而,如果你的实现太糟糕了,就很难确定这种方法能否奏效)。


1
不行。getline 无法处理模糊的分隔符。 - spraff
阅读问题:输入可能包含任一换行符类型。 - spraff
@spraff:我看了这个问题,但我也理解流(应该)如何工作。公平地说,它认为什么是行的结尾是由实现定义的,但如果你在一个平台上,其中'\r'或'\n'应该被视为行的结尾,那么任何一个都应该被读取为换行符(在文本/翻译模式下)。 - Jerry Coffin
@Jerry Coffin:要记住的一件事是,某些文件格式(例如永远邪恶的PDF)允许并且通常在其中混合使用换行符。因此,这不仅仅是一个平台问题 :-/. - Evan Teran
@spraff:是的,它存在,但你正在错误的地方寻找。正如我所说,这是底层流的一个特性。然而,有关流的相关描述都是以你将传递给fopen的模式字符串的等效方式给出的,因此你需要查看C标准(C99中的§7.19.2/2)。 - Jerry Coffin
显示剩余5条评论

4

好的,以下是一种实现方法。基本上,我已经编写了一个std::getline的实现,该实现接受谓词而不是字符。这样可以完成三分之二的工作:

template <class Ch, class Tr, class A, class Pred>
std::basic_istream<Ch, Tr> &getline(std::basic_istream<Ch, Tr> &is, std::basic_string<Ch, Tr, A>& str, Pred p) {

    typename std::string::size_type nread = 0;      
    if(typename std::istream::sentry(is, true)) {
        std::streambuf *sbuf = is.rdbuf();
        str.clear();

        while (nread < str.max_size()) {
            int c1 = sbuf->sbumpc();
            if (Tr::eq_int_type(c1, Tr::eof())) {
                is.setstate(std::istream::eofbit);
                break;
            } else {
                ++nread;
                const Ch ch = Tr::to_char_type(c1);
                if (!p(ch)) {
                    str.push_back(ch);
                } else {
                    break;
                }
            }
        }
    }

    if (nread == 0 || nread >= str.max_size()) {
        is.setstate(std::istream::failbit);
    }

    return is;
}

使用类似于这个函数对象的函数:
struct is_newline {
    bool operator()(char ch) const {
        return ch == '\n' || ch == '\r';
    }
};

现在,唯一需要确定的是你是否以 '\r' 结尾,如果是,则如果下一个字符是 '\n',只需将其消耗并忽略即可。 编辑:为了将所有内容放入功能解决方案中,这里提供一个示例:
#include <string>
#include <sstream>
#include <iostream>

namespace util {

    struct is_newline { 
        bool operator()(char ch) {
            ch_ = ch;
            return ch_ == '\n' || ch_ == '\r';
        }

        char ch_;
    };

    template <class Ch, class Tr, class A, class Pred>
        std::basic_istream<Ch, Tr> &getline(std::basic_istream<Ch, Tr> &is, std::basic_string<Ch, Tr, A>& str, Pred &p) {

        typename std::string::size_type nread = 0;

        if(typename std::istream::sentry(is, true)) {
            std::streambuf *const sbuf = is.rdbuf();
                str.clear();

            while (nread < str.max_size()) {
                int c1 = sbuf->sbumpc();
                if (Tr::eq_int_type(c1, Tr::eof())) {
                    is.setstate(std::istream::eofbit);
                    break;
                } else {
                    ++nread;
                    const Ch ch = Tr::to_char_type(c1);
                    if (!p(ch)) {
                        str.push_back(ch);
                    } else {
                        break;
                    }
                }
            }
        }

        if (nread == 0 || nread >= str.max_size()) {
            is.setstate(std::istream::failbit);
        }

        return is;
    }
}

int main() {

    std::stringstream ss("this\ris a\ntest\r\nyay");
    std::string       item;
    util::is_newline  is_newline;

    while(util::getline(ss, item, is_newline)) {
        if(is_newline.ch_ == '\r' && ss.peek() == '\n') {
            ss.ignore(1);
        }

        std::cout << '[' << item << ']' << std::endl;
    }
}

我对原始示例进行了一些小修改。现在,Pred p参数是一个引用,以便谓词可以存储一些数据(特别是上次测试的最后一个char)。同样地,我使谓词operator()非常量,以便它可以存储该字符。
在主函数中,我有一个字符串在std::stringstream中,其中包含所有3个版本的换行符。我使用我的util::getline,如果谓词对象表示最后一个char'\r',那么我将peek()向前并忽略1个字符,如果它碰巧是'\n'

谢谢,我很感激你的努力。我很惊讶居然没有一个著名的一行代码可以解决这个问题! - spraff

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