读取文件时如何在到达文件末尾后继续读取

3

我有一个名为readNextString(ifstream &file , char* &pBuffer)的函数,它从文件中提取下一个字符串,直到达到',''\n',并移除字符串开头和结尾的空格,将剩余部分保存在pBuffer中,并返回true(如果一切正常) - 否则返回false。这个函数在遇到文件结尾之前都能正常工作,但是当eof标志被设置时,无法移动get指针。我尝试了以下方法:

if(file.eof())
{
   file.clear();
   file.seekg(0 , ios::end)
}

删除字符串末尾的空格后,出现了问题。这个函数提取了没有空格的字符串,但是会进入无限循环。

我的实际问题是:如何检查下一个字符是否为EOF,如果不能,是否有任何替代方法?

以下是我的实际函数:

bool readNextString(ifstream &file , char* &pBuffer)
{
    if(file.eof()){
        return false;
    }
    for(; file.good() && isWhitespace(file.peek()) && !file.eof() ; file.seekg(1 , ios::cur))
        ;
    if(file.eof()){
        cout << "The file is empty.\n";
        return false;
    }else{
        streamoff startPos = file.tellg();
        cout << "startPos : " << startPos << endl;
        for(;file.good() && file.peek()!='\n' && file.peek()!=',' && file.peek()!= EOF; file.seekg(1 , ios::cur))
            ;
        streamoff A = file.tellg();
        cout << "A : " << A << endl;
        file.seekg(-1 , ios::cur);
        for(;file.good() && isWhitespace(file.peek()) ; file.seekg(-1 , ios::cur))
            ;
        file.seekg(2 , ios::cur);
        streamoff endPos = file.tellg();
        cout << "endPos : " << endPos << endl;
        pBuffer = new char[endPos-startPos];
        if(pBuffer)
        {
            file.seekg(startPos , ios::beg);
            file.get(pBuffer , endPos-startPos , ',' || '\n');
            for(;file.good() && file.peek()!='\n' && file.peek()!=',' && file.peek()!= EOF; file.seekg(1 , ios::cur))
                ;
            file.seekg(2 , ios::cur);
            streamoff temp = file.tellg();
            cout << "temp : " << temp << endl;
            return true;
        }else{
            cout << "Error! Not enough memory to complete the task.\nPlease close some applications and try again.\n";
            return false;
        }
    }
}

这是我所说的一个地方:

void printCities()
{
    ifstream city ;
    city.open("cities.txt", fstream::in);
    if(city.is_open())
    {
        char *currCity;
        int counter = 1;
        while(readNextString(city , currCity))
        {
            cout << counter++ << ". " << currCity << endl;
            delete[] currCity;
            currCity = NULL;
        }
        if(city.eof())
            cout << "There are no cities added.\n";
        city.close();
    }else
        cout << "Error by opening 'cities.txt'.Make sure that the file exist and try again.\n";
}

希望我表达清楚了。如果您发现其他错误或可能的错误,我很高兴听到并从中学习。


请注意,您真的不想像这样从文件中解析字符串。要么将整个文件读入缓冲区,要么一次读取大块到缓冲区并在内存中解析它。不要进行百万次读取。速度慢而且实际上更加复杂。 - David
@Dave 逐个字符地读取并不一定慢;istream 进行了缓冲。然而,使用 seek 是慢的;在许多实现中,seek 将导致缓冲区丢失,并且每个字符需要两个系统调用。(它不必这样,但是在缓冲区内进行的寻址应该足够少,以至于库不会对它们进行优化。) - James Kanze
3个回答

3
如何检查下一个字符是否为EOF?
像这样:
if (file.peek() == EOF)
{
    // next char is EOF
    ...
}

是的,那段代码在我的程序里,但它不能正常工作。 问题出现在第二个for循环中,当我试图找出下一个',','\n'或在这种情况下的EOF时。当我检查get指针(tellg)的位置时,在我的代码中我将其保存为'A',并且它告诉我该位置为-1... :( - dragonator
我不确定,但我认为tellg失败是因为您尝试读取文件末尾之后的内容。在调用peek之前,请尝试调用tellg。 - john
或者尝试简化一下。我没有理解你的问题,也没有看到其他答案,但是你的代码似乎非常复杂。 - john
@john 那是个好观点。一旦 file.peek() 返回文件结尾,tellg 就无效了,并且会失败。在输入文件上使用多次传递以进行解析的整个策略都是错误的。 - James Kanze

2
首先,不要使用seek跳过空格。只需获取字符并完成即可。
其次,您似乎误解了istream :: good()和istream :: eof()的含义。永远没有任何时候适用istream :: good(),而istream :: eof()通常仅在输入失败后才适用。至于您循环跳过空格的问题,通常的解决方案是:
while ( isspace( file.peek() ) && file.peek() != EOF ) {
    file.get();     //  ignore read character...
}

其他循环也是类似的,只是你不想忽略已读取的字符。例如,要收集直到下一个','的字符:

std::string field;
while ( file.peek() != ',' && file.peek() != EOF ) {
    field.push_back( file.get() );
}

(而你的file.get( pBuffer, endPos - startPos, ',' || '\n' )肯定不会按照你的期望工作;表达式',' || '\n'将始终评估为true,当转换为char时,为'\01。)
最后,虽然上述策略可行,但最好将更大的文本单元输入到std::stream中,并进行解析。如果文本是基于行的,请使用以下内容:
std::string line;
while ( std::getline( file, line ) ) {
    //  Parse line, using std::istringstream if appropriate,
    //  although this doesn't seem to be the case for your code.
}

这比你正在做的事情简单了几个数量级。

谢谢您提供详细的答案,但我认为它对我帮助不大... 1.您给我的第一段代码几乎与我的相同,但它不能正常工作。那个file.peek() != EOF失败了。 2.第二段代码实际上可能有所帮助... 3.关于我的代码-我没有很好地理解您的意思,但我认为它可以正常工作,因为问题只出现在文件末尾... 4.至于getline() - 那是我的第二选择,但我想知道如何使用seekg()来完成这个操作...肯定有一种方法可以用这种方法完成... 再次感谢您的帮助 :) - dragonator
@dragonator file.peek()永远不会失败。如果file.peek()返回EOF,那么要么是因为你已经到达文件末尾,要么是因为你已经遇到了某种错误。而这些不同的代码片段是为了解释不同的错误:一旦你到达文件末尾,file就会失败,任何其他操作(包括寻找)都无效。至于第3点,我引用的行的结束条件是字符0x01。或者你可以读取确切的计数:你可能想要的是file.read(pBuffer, endPos - startPos)(但这仍然不是一个好的解决方案)。 - James Kanze
如果目标只是为了获得对 gseek() 的经验:你可能需要在每次寻找之前立即调用 file.clear()。或者使用 C++11(它指定在任何其他操作之前应清除 seekg 中的 eofbit)。 - James Kanze

0

好的,我用另一种方式实现了!getline() 赢了! :D 这是我的代码(这次更易读):

bool readNextString(ifstream &file , char pBuffer[] )
{
    while(isWhitespace(file.peek()) && !file.eof())
        file.ignore(1);
    if(!file.eof())
    {
        streamoff start = file.tellg();
        stringstream toComma;
        if(file.getline(pBuffer , 200 , ','))
        {
            toComma << pBuffer;
            toComma.getline(pBuffer ,200, '\n');
            int i=strlen(pBuffer)-1;
            for(; isWhitespace(pBuffer[i]) ;i--)
                ;
            pBuffer[i+1] = '\0';
            file.clear();
            file.seekg(start + strlen(pBuffer) , file.beg);
            return true;
        }else return false;
    }
    return false;
}

我也对我的另一个函数进行了一些更改:

void printCities()
{
    ifstream city ;
    city.open("cities.txt", fstream::in);
    if(city.is_open())
    {
        if(!isEmpty(city))
        {
            char currCity[200];
            int counter = 1;
            while(readNextString(city , currCity) && counter < 10)
                cout << counter++ << ". " << currCity << endl;
        }else
            cout << "There are no cities added.\n";
        city.close();
    }else
        cout << "Error by opening 'cities.txt'.Make sure that the file exist and try again.\n";
}

函数isEmpty(ifstream &file)如果文件为空则返回true,否则返回false。

感谢大家的帮助! 最好的祝福!


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