1

此代码从输入文件中接受学生姓名、父亲姓名、卷号和年龄,并将其以可呈现的方式放在输出文件中。

在此代码中,当输入文件的内容为:

Vicky
Mohan
20094567
22   Ricky
Rahul
20091234
21

它工作正常。

但如果他们是:

Vicky
Mohan
20094567
22
Ricky
Rahul
20091234
21

它进入一个无限循环。有什么建议么??

ifstream inps("input", ios::in);
outs.open("output",ios::app);

string line;
int data,count=1;

for(getline(inps,line);line!="";getline(inps,line))
{
    count++;

    s1.setName(line);
    getline(inps,line);
    s1.setFatherName(line);
    inps >> data;
    s1.setRollNo(data);
    inps >> data;
    s1.setAge(data);

    outs.open("output",ios::app);
    outs << "Student name: " << s1.getName() << endl;
    outs << "Father’s name: " << s1.getFatherName() << endl;

    outs << "Roll number: " << s1.getRollNo() << endl;
    outs << "Age: " << s1.getAge() << endl << endl;
}

inps.close();
outs.close();
4

3 回答 3

6

这是因为你如何阅读输入。你永远不会真正检查它是否成功。

你需要做例如

while (std::getline(...))
{
    ...
}
于 2013-08-06T12:02:25.100 回答
5

您描述的症状的原因是您将格式化输入与getline. 还有一个基本问题是您永远不会检查任何输入是否成功。

真正的问题出现在这些行之后inps >> data :这些行跳过空格并读取一个int,仅此而已。特别是,它们会'\n'在流中留下任何尾随空格,包括字符。因此,在您的第二种输入情况下,在read 之后22,流中仍然有 a '\n',它将终止下一次调用 getline(而不是read ,而是" Ricky"read "")。这会导致输入变得不同步,这很快就会导致您inps >> data在流位于"Rahul". 尝试int在输入"Rahul"失败时读取一个,并且失败是粘性的;它会一直存在,直到您重置它,并且所有进一步的尝试都是无操作的。既然你已经读过一些东西line一次,它永远不会变空,你会永远循环,什么都不做。

第一个也是最重要的变化是在每次 输入后检查输入是否成功,如果没有,不要尝试进一步阅读。(文件的结构是这样的,如果出现错误,您可能无法可靠地重新同步。否则,尝试重新同步并继续是一个很好的策略,这样您就可以在输入中捕获多个错误。)

您需要做的第二件事是确保'\n'在输入整数时读取完整的行(包括 )。有两种方法:经典的方法是使用 ,然后用行getline初始化 an ,然后使用 this 输入。(这允许额外的错误检查,例如该行中没有额外的垃圾。)或者,您可以调用,它将提取并忽略字符直到(也被提取)。std::istringstreamintinps.ignore( std::numeric_limits<std::streamsize>::max(), '\n' );'\n'

编辑:

重读时,我突然想到我的文字描述并不是那么清楚,所以这是逐步解释中发生的事情:

  • 第一次通过循环,一切都按预期工作, 输入位置紧随其后"22"(这是最后一个输入)。

  • getline循环顶部的 被调用。它将返回"22"该行的结尾和结尾之间的所有字符。如果"22"紧随其后的是新行,这将导致空行,终止循环(尽管还有更多数据要读取)。如果后面有多余的字符"22"(比如空格左右),那么这些将被读取为行。

  • 假设有多余的字符,然后您将“Ricky”读作父亲的名字,并inps >> data为字符串上的卷号做"Rahul"。这会失败,并将流设置为错误条件,这会导致所有进一步的操作都是无操作的。

  • 所以当你下一次到达循环的顶部时,getline是一个空操作,之前的内容line不变,你再次进入循环。一次又一次,因为在您清除错误之前,所有操作都将是无操作的。所有变量都保持其旧值。

最简单的解决方案可能是 Neil Kirk 在评论中建议的:将整个文件读入 std::vector 行,并解析这些:

class Line
{
    std::string myContents;
public
    friend std::istream& operator>>( std::istream& source, Line& obj )
    {
        std::getline( source, obj.myContents );
        return source;
    }
    operator std::string() const { return myContents; }
};

// ...
std::vector<Line> lines( (std::istream_iterator<Line>( inps )),
                         (std::istream_iterator<Line>()) );

但是,如果您想即时读取文件(比如说因为它可能太大而无法放入内存,或者仅仅是因为它是一个很好的学习练习):

while ( std::getline( inps, line ) && !line.empty() ) {
            //  but do you really what the second condition.
            //  if so, you should probably provide
            //  a function which will ignore whitespace.
    s1.setName( line );
    if ( std::getline( inps, line ) ) {
        s1.setFatherName( line );
    }
    if ( std::getline( inps, line ) ) {
        std::istringstream s( line );
        int data;
        if ( s >> data ) {
            s1.setRollNo( data );
        }
    }
    if ( std::getline( inps, line ) ) {
        std::istringstream s( line );
        int data;
        if ( s >> data ) {
            s1.setAge( data );
        }
    }
}

这是非常简洁的。它仍然需要额外的错误检查,并且您可能希望跟踪行号,以便您可以将其与任何错误消息一起输出。但它应该为您指明正确的方向。

编辑2:

此外,您不希望每次都通过循环打开输出文件。尝试打开已经打开的std::ofstream 文件将失败,如上所述,一旦流失败,所有进一步尝试使用它都是无操作的。

于 2013-08-06T12:52:57.383 回答
2

代替

for(getline(inps,line);line!="";getline(inps,line))

while (getline(inps, line))
于 2013-08-06T12:04:35.253 回答