如何到达文件的末尾

时间:2014-01-11 06:03:12

标签: c++ file struct

我有一个函数可以从文件中获取一些信息并将其放在一个结构中。我设置了一个循环(两者),条件是它一直持续到达文件末尾。 但它不起作用。

void FileToStructProfessors()
{
     int n;
     fstream CurrentFile("professor.txt");
     if (!CurrentFile)
     {
         cout <<"can't open file";
     }
     else
     {
         CurrentFile.seekp(5);
         /*for(n=0;CurrentFile.eof();n++)
         {
             CurrentFile>>P1[n].FirstName;
             CurrentFile>>P1[n].LastName;
             CurrentFile>>P1[n].PID;
             CurrentFile>>P1[n].Major;
             CurrentFile>>P1[n].Level;
             CurrentFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
             cout<<P1[0].FirstName<<endl;

         }*/
         n=0;
         while(!CurrentFile.eof())
         {
             CurrentFile>>P1[n].FirstName;
             CurrentFile>>P1[n].LastName;
             CurrentFile>>P1[n].PID;
             CurrentFile>>P1[n].Major;
             CurrentFile>>P1[n].Level;
             CurrentFile>>P1[n].Date;
             CurrentFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
             cout<<P1[3].FirstName<<endl; 
             n++;               
         }
     }
     CurrentFile.close();
}

professor.txt

4

hamed
hamidi
1001
civil
associate professor
91/02/15

morteza
jafari
1005
computer
assistant professor
91/01/20

mahdi
foladi
1006
computer
professor
90/10/10

mostafa
mohammadi
1009
electronic
assistant professor
91/05/5
教授结构的

struct Professors
{
       string FirstName,LastName;
       long long int PID;
       string Major,Level,Date;
}P1[100];

3 个答案:

答案 0 :(得分:3)

您的问题是您正在阅读尝试阅读行,但CurrentFile >>忽略所有空格,包括空格。所以,当你读到第一位教授时:

med
hamidi
1001
civil
associate professor
91/02/15

你会得到:

FirstName = med
LastName = hamidi
PID = 1001
Major = civil
Level = associate
Date = professor

“关联”和“教授”之间有一个空格,因此CurrentFile >> P1[n].Level只会选择“关联”,因为CurrentFile >>会看到空格并停止。然后你的下一个CurrentFile >>选择“教授”而不是你想要阅读的日期。将此复合到整个文件中,您将获得大量无意义的输入。

您可以使用getline(CurrentFile, *string you want to read in*)解决此问题,如下例所示。 请注意,这不是生产质量的代码,只是一个简单的玩具示例,说明getline旨在让您继续推进项目。

void FileToStructProfessors()
{
    int n;
    fstream CurrentFile("professor.txt");
    if (CurrentFile.fail())
    {
        cout <<"can't open file";
        // Put a return here so you don't
        // try to `.close()` the file later.
        return;
    }
    else
    {
        while(CurrentFile.good())
        {
            getline(CurrentFile, P1[n].FirstName);
            getline(CurrentFile, P1[n].LastName);

            // PID is an integer, and getline doesn't work with integers,
            // only strings. You can try simply 'CurrentFile >> P1[n].PID;',
            // but this will mess up the next getline (try it yourself if you
            // want). We can get around this using a stringstream, so put
            // '#include <sstream>' at the top of your file.
            string s;  
            getline(CurrentFile, s);
            istringstream iss(s);
            iss >> P1[n].PID;

            getline(CurrentFile, P1[n].Major);
            getline(CurrentFile, P1[n].Level);
            getline(CurrentFile, P1[n].Date);

            // This is to read past the blank line that
            // separates your entries.
            getline(CurrentFile, s);

            // Print stuff.

            n++;               
        }
    }

    CurrentFile.close();
}

顺便说一下,我删除了

  1. CurrentFile.seekp(5)
  2. CurrentFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  3. 注意

    我的示例不安全/正确,因为这些getline调用或CurrentFile >>语句中的任何一个都可能遇到错误,并且都没有检查错误。我写这个是一个相对简单易懂的解释,为什么你的代码有问题。对于安全/正确的代码,请查看0x499602D2's answer

答案 1 :(得分:2)

执行输入的条件不应以获取文件末尾为基础。仅在输入失败之前执行输入是执行提取的常规条件。否则会导致不必要的行为。

因此,您应检查流是否未设置std::ios_base::failbitstd::ios_base::badbit。检查这两个位是必要的,因为failbit表示解析流无法成功提取的位置时出错,而badbit表示外部设备出现问题(可能是不可恢复的错误)。两者都导致无法提取。但是这一切都需要在尝试输入之后完成。

以上是使用您的代码表示的上述任​​务:

while (  CurrentFile >> P1[n].FirstName &&
         CurrentFile >> P1[n].LastName  &&
         CurrentFile >> P1[n].PID       &&
         CurrentFile >> P1[n].Major     &&
         std::getline(CurrentFile >> std::ws, P1[n].Level) &&
         std::getline(CurrentFile >> std::ws, P1[n].Date) )

注意我如何使用std::getline()更改了最后两次提取。这是因为文件中的相应数据包含格式化运算符用作分隔符的空格。日期使用斜杠,当使用格式化输入时,斜杠也会分隔。在此之后,LevelDate数据成员必须为std::string,您需要使用无格式输入。

如果任何一个提取失败,则流将设置适当的位。然后,流将使用operator bool()(或operator void*() pre-C ++ 11隐式转换为布尔值,然后将其升级为布尔值)。布尔操作将使用!this->fail()检查流状态(检查badbitfailbit),如果函数返回true,则整个while循环表达式为false,不会进行剩余的提取(因为短路评估)。

如果流仍处于良好状态(!this->fail()返回true),则将执行循环体。如果流在执行提取时遇到EOF字符,则将设置eofbit,但循环不会停止,直到稍后尝试的输入操作无法成功提取。这是理想的行为。

答案 2 :(得分:0)

改善您的计划:

这是你问题的症结所在。您不了解如何执行流输入。我不知道是谁教过你while (!file.eof()),但几乎总是走错路。

你的节目很好,但并不完美。如果您想知道如何改进您的课程,请继续阅读,否则我希望我的其他答案有所帮助。

您没有向我们展示相关代码,但我假设P1是一个默认大小的静态数组(大小为4)。由于索引n随文件内容的增加而增加,因此您的代码可能存在危险。如果要将多于4个“组”数据提取到数组中,则程序将调用未定义的行为。

要避免此潜在问题,您可以使用std::vector<T>之类的动态容器。它将分配适当大小的内部数组,并在需要时动态调整大小。您可以在构造中指定大小,如下所示:

CurrentFile >> size;
std::vector<Professor> P1(size);

向此容器添加数据就像P1.push_back(p)一样简单。你不再需要一个柜台了。


CurrentFile.seekp(5)没有意义,因为流用于输入,只会影响输出指针。如果要忽略第一个整数,可以简单地提取为虚拟变量。但由于我已经完成了上述工作,因此没有必要。如果你试图忽略文件顶部的整数之间的空格,那么第一个Professor的名称也是如此,因为格式化的提取器会自动从输入序列中丢弃前导空格。您无需担心无意中提取的空格。

这也使得以下代码变得不必要:

CurrentFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

上面的代码出现在你的while循环中(我相信)试图丢弃空格和换行符,以便提取的下一个字符是下一个教授的名字。出于同样的原因,这不是必需的。


最后,为了便于提取到Project类型的对象,建议您使用运算符重载来实现自己的提取器。它使代码的其余部分保持干净和连贯。