【问题标题】:Entering in an infinite loop while reading a file读取文件时进入无限循环
【发布时间】:2013-08-07 10:06:25
【问题描述】:

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

在这段代码中,当输入文件的内容是:

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();

【问题讨论】:

  • 在这种情况下,while 循环会更有意义。
  • 我试过了,但同样的问题
  • 你不要缩进你的代码
  • idon'tunderstandthecdoeformatting
  • 不要在循环中再次调用 getline!制作一个字符串向量并推回每一行。然后,您可以在闲暇时索引该向量。

标签: c++ file-io iostream infinite-loop


【解决方案1】:

这是因为您阅读输入的方式。你永远不会真正检查它是否成功。

你需要做例如

while (std::getline(...))
{
    ...
}

【讨论】:

  • 假设输入格式正确,这很好,因为循环体内有更多的读取。但是,原始代码也做出了这样的假设。
  • @R.MartinhoFernandes 至少不会进入死循环。
  • @JoachimPileborg 它不会进入无限循环,但仍会将未定义行为的情况留在循环内。并且std::getline 不会在她的情况下擦除字符串,因为直到 构建哨兵对象之后才会发生擦除,并且只有当哨兵对象测试正常。 (根据她的代码,它不会。)
【解决方案2】:

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

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

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

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

编辑:

重读时,我突然发现我的文字描述并不是全部 很清楚,所以这是逐步解释中发生的事情:

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

  • 调用循环顶部的getline。它会 返回"22" 和结尾之间的所有字符 那条线。如果"22" 后面紧跟着一个新行, 这应该导致一个空行,终止循环 (虽然还有更多的数据要读取)。如果有 "22" 之后的额外字符(比如空格左右),然后 这些将被读取为行。

  • 假设有多余的字符,然后您阅读 “Ricky”作为父亲的名字,并为inps &gt;&gt; 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 );
        }
    }
}

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

EDIT2:

另外,你不想每次都打开输出文件 循环。试图打开一个已经打开的std::ofstream 将失败,如上所述,一旦流失败,所有进一步 尝试使用它是无操作的。

【讨论】:

    【解决方案3】:

    替换

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

    while (getline(inps, line))
    

    【讨论】:

    • 失去一个条件。让它while (getline(inps, line) &amp;&amp; !line.empty())
    • for (string line; getline(inps, line) &amp;&amp; !line.empty();) 将字符串变量限制在循环范围内。
    • @Snps 这里真的没必要限制范围。
    • 虽然这摆脱了无限循环,但它仍然不会产生正确的代码(并且仍然会在循环内留下正式未定义的行为)。跨度>
    • @JamesKanze 同意,但他仍然没有在循环外使用变量。尽可能限制变量的范围是 IMO 的一个很好的编码约定。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多