在读取文件时进入无限循环

1 c++ file-io iostream infinite-loop

此代码接受输入文件中的学生姓名,父亲姓名,滚动号和年龄,并以可显示的方式将其放在输出文件中.

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

Vicky
Mohan
20094567
22   Ricky
Rahul
20091234
21
Run Code Online (Sandbox Code Playgroud)

它工作正常.

但如果它们是:

Vicky
Mohan
20094567
22
Ricky
Rahul
20091234
21
Run Code Online (Sandbox Code Playgroud)

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

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();
Run Code Online (Sandbox Code Playgroud)

Som*_*ude 6

这是因为你如何阅读输入.你从来没有真正检查它是否成功.

你需要做例如

while (std::getline(...))
{
    ...
}
Run Code Online (Sandbox Code Playgroud)


Jam*_*nze 5

您描述的症状的原因是您正在混合格式化输入getline.还有一个基本问题,你永远不会检查是否有任何输入成功.

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

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

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

编辑:

在重读时,我发现我的文字描述并不是那么清楚,所以这是在逐步探索中发生的事情:

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

  • getline在循环的顶部被调用.它将返回"22"该行的末尾之间的所有字符.如果"22"后面紧跟一个新行,这将导致一个空行,终止循环(尽管还有更多数据要读取).如果在"22"(例如空白左右)之后有额外的字符,那么这些将被读作行.

  • 假设有额外的字符,那么你可以将"Ricky"作为父亲的名字,并inps >> data为字符串上的卷号做"Rahul".这会失败,并将流设置为错误条件,这会导致所有进一步的操作都是无操作.

  • 因此,当您下次到达循环顶部时,它getline是一个无操作,之前的内容line未更改,您再次进入循环.再次,再次,因为在您清除错误之前,所有操作都将是无操作.所有变量都保留其旧值.

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

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>()) );
Run Code Online (Sandbox Code Playgroud)

但是,如果你想动态阅读文件(比如因为它可能太大而无法放入内存,或者只是因为它是一个很好的学习练习):

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 );
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这非常简洁.它仍然需要额外的错误检查,您可能希望跟踪行号,以便您可以输出任何错误消息.但它应该指出你正确的方向.

EDIT2:

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