使用任意分隔符

时间:2017-08-01 21:23:13

标签: c++ getline

我遇到了使用C ++从文件中读取msg的问题。通常人们所做的是创建文件流,然后使用getline()函数来获取消息。 getline()函数可以接受一个附加参数作为分隔符,以便它返回由新分隔符分隔的每个“行”,但不是默认的'\ n'。但是,此分隔符必须是char。在我的用例中,msg中的分隔符可能是“| - |”之类的东西,所以我尝试得到一个解决方案,使它接受一个字符串作为分隔符而不是一个字符。

我已经搜索了StackOverFlow,发现了一些有趣的帖子。 Parse (split) a string in C++ using string delimiter (standard C++) 这个给出了使用string::find()string::substr()来解析任意分隔符的解决方案。但是,那里的所有解决方案都假设输入是一个字符串而不是一个流,在我的情况下,文件流数据太大/浪费,不能一次适应内存所以它应该用msg读取msg(或大量的msg at一旦)。

实际上,通读std::getline()函数的gcc实现,看起来处理大小写分隔符更容易烧焦。因为每次加载一大块字符时,您总是可以搜索分隔符并将它们分开。虽然如果分隔符不止一个字符不同,分隔符本身可能会跨越两个不同的块并导致许多其他角点情况。

不确定是否有其他人在此之前以及如何优雅地处理此类要求。似乎有一个像istream& getNext (istream&& is, string& str, string delim)这样的标准函数会很好吗?这对我来说似乎是一个普遍的用例。为什么这个不在标准库中,以便人们不再单独实现自己的版本?

非常感谢

3 个答案:

答案 0 :(得分:1)

STL本身并不支持你要求的东西。您必须编写自己的功能(或找到第三方功能),以满足您的需求。

例如,你可以使用std::getline()阅读到你的分隔符的第一个字符,然后用std::istream::get()阅读后面的字符并将它们与你的分隔符的其余部分。例如:

std::istream& my_getline(std::istream &input, std::string &str, const std::string &delim)
{
    if (delim.empty())
        throw std::invalid_argument("delim cannot be empty!"); 

    if (delim.size() == 1)
        return std::getline(input, str, delim[0]);

    str.clear();

    std::string temp;
    char ch;
    bool found = false;

    do
    {
        if (!std::getline(input, temp, delim[0]))
            break;

        str += temp;

        found = true;

        for (int i = 1; i < delim.size(); ++i)
        {
            if (!input.get(ch))
            {
                if (input.eof())
                    input.clear(std::ios_base::eofbit);

                str.append(delim.c_str(), i);
                return input;
            }

            if (delim[i] != ch)
            {
                str.append(delim.c_str(), i);
                str += ch;
                found = false;
                break;
            }
        }
    }
    while (!found);

    return input;
}

答案 1 :(得分:0)

如果您可以逐字节读取,则可以构建有限状态机的状态转换表实现以识别您的停止条件

std::string delimeter="someString";
//initialize table with a row per target string character, a column per possible char and all zeros
std::vector<vector<int> > table(delimeter.size(),std::vector<int>(256,0));
int endState=delimeter.size();
//set the entry for the state looking for the next letter and finding that character to the next state
for(unsigned int i=0;i<delimeter.size();i++){
    table[i][(int)delimeter[i]]=i+1;
}

现在你可以像这样使用它

int currentState=0;
int read=0;
bool done=false;
while(!done&&(read=<istream>.read())>=0){
    if(read>=256){
        currentState=0;
    }else{
        currentState=table[currentState][read];
    }
    if(currentState==endState){
        done=true;
    }
    //do your streamy stuff
}

授予此权限只有在分隔符使用扩展ASCII时才有效,但是对于像你的例子这样的东西它会正常工作。

答案 2 :(得分:0)

似乎最简单的方法是创建类似getline()的内容:读取分隔符的 last 字符。然后检查字符串是否足够长以用于分隔符,如果是,则它是否以分隔符结束。如果不是,请继续阅读:

std::string getline(std::istream& in, std::string& value, std::string const& separator) {
    std::istreambuf_iterator<char> it(in), end;
    if (separator.empty()) { // empty separator -> return the entire stream
        return std::string(it, end);
    }
    std::string rc;
    char        last(separator.back());
    for (; it != end; ++it) {
        rc.push_back(*it);
        if (rc.back() == last
            && separator.size() <= rc.size()
            && rc.substr(rc.size() - separator.size()) == separator) {
            return rc.resize(rc.size() - separator.size());
        }
    }
    return rc; // no separator was found
}