在我清除它之前我不需要取消 istream 吗?

Shouldn't I need to unget an istream before I clear it?

问题的简短版本

如果我像这样读取数据:

    while (in >> x) {
      hw.push_back(x);
    }
    // clear the stream so that input will work for the next student
    in.clear();

其中 instd::istreamxdoublehwvector<double>。我不需要放回任何导致我跳出 while 循环的读取尝试吗?否则,我的下一次读取 in 不会跳过一些数据吗?

完整问题

我正在尝试读取一个包含字符串和一系列数字的文本文件。我正在将这些数据处理成一个结构,其中包含该数据的成员属性。

输入数据如下所示: Moore 75 85 77 59 0 85 75 89

此数据代表学生的姓名、他们的期末考试成绩、他们的期中考试成绩和一些作业。

要读取此类数据,我有以下内容:

#include <boost/format.hpp>
#include <iostream>
#include <string>

using std::istream;
using std::vector;
using std::cout;
using std::string;
using std::cin;

struct Student_info {
  std::string name;
  double midterm, final;
  std::vector<double> homework;
};

istream& read(istream&, Student_info&);
istream& read_hw(istream&, vector<double>&);

istream& read(istream& is, Student_info& s) {
  // Read and store th studen's name and midterm and final exam grades
  is >> s.name >> s.midterm >> s.final;

  read_hw(is, s.homework); // read and store all the student's homework grades
  return is;
}

istream& read_hw(istream& in, vector<double>& hw)
{
  if (in) {
    // get rid of previous contents
    hw.clear();

    // read homework grades
    double x;
    while (in >> x) {
      hw.push_back(x);
    }
    // clear the stream so that input will work for the next student
    in.clear();
  }
  return in;
}

简而言之,如果我的理解是正确的,我会先看名字,然后是两次双打(期末考试和期中考试),然后是作业。

我知道什么时候停止阅读 vector<double> 家庭作业成绩如下:

    while (in >> x) {
      hw.push_back(x);
    }
    // clear the stream so that input will work for the next student
    in.clear();

这对我来说看起来非常合理,但是当我读入一系列数据行时,数据没有被正确读入。

例如,使用以下输入:

Moo 100 100 100 100 100 100 100 100
Moore 75 85 77 59 0 85 75 89
Norman 57 78 73 66 78 70 88 89

我得到以下输出:

Name: Moo, Midterm: 100, Final: 100, Num HW: 6
Name: Moore, Midterm: 75, Final: 85, Num HW: 6
Name: orman, Midterm: 57, Final: 78, Num HW: 6

注意名字是 orman,不是 Norman。缺少 N。这不是打字错误,这是我试图理解的问题。

在我看来我需要 "unget",尽管当我尝试直接调用 in.unget() 时它并没有改善。

下面是一些完整的输入数据和驱动程序的完整源代码,如果有人想亲自尝试的话:

全部输入数据

Moo 100 100 100 100 100 100 100 100
Moore 75 85 77 59 0 85 75 89
Norman 57 78 73 66 78 70 88 89
Olson 89 86 70 90 55 73 80 84
Peerson 47 70 82 73 50 87 73 71

Russel 72 87 88 54 55 82 69 87
Thomas 90 96 99 99 100 81 97 97
Vaughn 81 97 99 67 40 90 70 96
Westerly 43 98 96 79 100 82 97 96


Baker 67 72 73 40 0 78 55 70
Davis 77 70 82 65 70 77 83 81
Edwards 77 72 73 80 90 93 75 90
Franklin 47 70 82 73 50 87 73 71

Jones 77 82 83 50 10 88 65 80
Harris 97 90 92 95 100 87 93 91
Smith 87 92 93 60 0 98 75 90
Carpenter 47 90 92 73 100 87 93 91

Fail1 45 55 65 80 90 70 65 60
Fail2 55 55 65 50 55 60 65 60

完整的驱动程序源代码

#include <boost/format.hpp>
#include <iostream>
#include <string>

using std::istream;
using std::vector;
using std::cout;
using std::string;
using std::cin;

struct Student_info {
  std::string name;
  double midterm, final;
  std::vector<double> homework;
};

istream& read(istream&, Student_info&);
istream& read_hw(istream&, vector<double>&);

istream& read(istream& is, Student_info& s) {
  // Read and store th studen's name and midterm and final exam grades
  is >> s.name >> s.midterm >> s.final;

  read_hw(is, s.homework); // read and store all the student's homework grades
  return is;
}

istream& read_hw(istream& in, vector<double>& hw)
{
  if (in) {
    // get rid of previous contents
    hw.clear();

    // read homework grades
    double x;
    while (in >> x) {
      hw.push_back(x);
    }
    // clear the stream so that input will work for the next student
    in.clear();
  }
  return in;
}

int main() {

  vector<Student_info> students;
  Student_info record;
  string::size_type maxlen = 0;

  while (read(cin, record)) {
    // find length of longest name
    cout << boost::format("Name: %1%, Midterm: %2%, Final: %3%, Num HW: %4%\n") % record.name % record.midterm % record.final % record.homework.size();
    students.push_back(record);
  }

  return 0;
}

使用完整的输入数据,输出如下所示(注意许多名称不正确):

Name: Moo, Midterm: 100, Final: 100, Num HW: 6
Name: Moore, Midterm: 75, Final: 85, Num HW: 6
Name: orman, Midterm: 57, Final: 78, Num HW: 6
Name: Olson, Midterm: 89, Final: 86, Num HW: 6
Name: rson, Midterm: 47, Final: 70, Num HW: 6
Name: Russel, Midterm: 72, Final: 87, Num HW: 6
Name: Thomas, Midterm: 90, Final: 96, Num HW: 6
Name: Vaughn, Midterm: 81, Final: 97, Num HW: 6
Name: Westerly, Midterm: 43, Final: 98, Num HW: 6
Name: ker, Midterm: 67, Final: 72, Num HW: 6
Name: vis, Midterm: 77, Final: 70, Num HW: 6
Name: wards, Midterm: 77, Final: 72, Num HW: 6
Name: ranklin, Midterm: 47, Final: 70, Num HW: 6
Name: Jones, Midterm: 77, Final: 82, Num HW: 6
Name: Harris, Midterm: 97, Final: 90, Num HW: 6
Name: Smith, Midterm: 87, Final: 92, Num HW: 6
Name: rpenter, Midterm: 47, Final: 90, Num HW: 6
Name: l1, Midterm: 45, Final: 55, Num HW: 6
Name: l2, Midterm: 55, Final: 55, Num HW: 6

更新 1

我尝试在打破以下 while 循环后添加 in.seekg(-1, in.cur);

    double x;
    while (in >> x) {
      hw.push_back(x);
    }

    // Going to try and get the istream back to where it was when I broke out of the while loop.
    in.seekg(-1, in.cur);

    // clear the stream so that input will work for the next student
    in.clear();

认为这会让我回到导致我跳出 while 循环的原因。但是仍然没有正确读入学生姓名

更新 2

我看到这里有一个几乎相同的问题:

Why is istream.clear() removing part of my strings while reading doubles and strings?

然而,公认的解决方案并没有解释为什么这里所做的是错误的——它只是提供了一个解决方法。

更新 3

我很欣赏所有的解决方法,但请考虑这个更有针对性的问题,为什么后面的每一行都不会在这里或那里缺少一个字母?只有部分线路可以。

通常不需要在标准流上使用 unget()。您遇到的问题是您需要知道何时停止阅读一行。函数 std::getline 就是为此目的而创建的。您可以遍历每一行,将该行存储在 std::istringstream 中,然后从那里解析出记录:

std::istream& read_hw(std::istream& in, std::vector<double>& hw) {
  hw.clear();
  std::string line;
  if (std::getline(in, line)) {
    hw.assign(
      std::istream_iterator<double>{std::istringstream{line} >> std::skipws}, {}
    );
  }
  return in;
}

奇怪提取的原因是字母 ABCDEFINP 可以出现在 double 中,而其他字母则不能。有关详细信息,请参阅 strtof spec

这是流 I/O 没有前瞻的基本问题。该标准指定(粗略地说)继续提取,直到找到目标类型中不能出现的字符,然后尝试转换提取的内容。多年来对规范进行了各种调整(包括将有效字符列表更改为双精度),但没有真正的解决方案。

没有规定在转换失败时放回字符,您将不得不使用不同的提取方法。正如另一个答案中所建议的那样:由于您的输入是面向行的(即换行符很重要),因此最好使用面向行的读取函数来读取一行,然后解析一行。您使用 >> until error 的方法无法换行(该运算符将所有空格视为相同)。