在清除 istream 之前,我不应该取消它吗?

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

提问人:EMiller 提问时间:1/19/2020 最后编辑:EMiller 更新时间:1/20/2020 访问量:93

问:

问题的简短版本

如果我像这样读取数据:

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

其中 是 a 、 是 和 是 .难道我不需要放回导致我跳出 while 循环的任何读取尝试吗?否则,我的下一次读取不会跳过一些数据吗?instd::istreamxdoublehwvector<double>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。这不是错别字,这就是我试图理解的问题。

在我看来,我需要“取消”,尽管当我尝试从字面上调用它时,它并没有改善事情。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

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

为什么 istream.clear() 在读取双精度和字符串时删除我的部分字符串?

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

更新 3

我很欣赏所有的解决方法,但考虑一下这个更集中的问题,为什么后面的每一行都没有在这里或那里缺少一个字母?只有一些行可以。

C++ 字符串 IOSTREAM CIN

评论

0赞 Michael Chourdakis 1/19/2020
读取/写入将文件指针向前推。
0赞 EMiller 1/19/2020
因此,当我因为读取不是双精度符的内容而退出 while 循环时,如何将文件指针移回以便读取下一行数据?
0赞 Michael Chourdakis 1/19/2020
这回答了你的问题吗?如何将istream的指针移动n个字符?
0赞 EMiller 1/20/2020
谢谢迈克尔。它部分回答了我的问题。但我仍然有点困惑,这是否是正确的行动方案。我看到的 Web 代码与我在这里发布的代码类似——当 istream 返回 False 然后调用 istream 时,它会中断一个 while 循环。但是我还没有看到人们移动istream的指针。clear
0赞 Michael Chourdakis 1/20/2020
通常,当您确切地知道根据之前的阅读会发现什么时,您就会从流中读取。如果你需要某种解析,你首先会在内存中读取它。

答:

1赞 David G 1/20/2020 #1

通常不需要在标准流上使用。你遇到的问题是你需要知道什么时候停止阅读一行。该功能就是为此目的而制作的。您可以遍历每一行,将该行存储在 中,然后从那里解析出记录:unget()std::getlinestd::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;
}
3赞 M.M 1/20/2020 #2

奇怪提取的原因是字母可以出现在 a 中,而其他字母不能。有关详细信息,请参见 strtof 规范ABCDEFINPdouble

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

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

评论

0赞 EMiller 1/20/2020
您能否提供一个链接来解释这些字符在 ?double
0赞 M.M 1/20/2020
@EMiller OK,添加了指向 strtof 规范的链接。 并且来自以 16 为基数的格式,并且可能有 or0pABCDEFINFNAN
0赞 EMiller 1/20/2020
啊哈。谢谢你。