假设我正在读取一个文件(具体来说是“infile.txt”)来计算测验分数的平均值。正在读取的文件如下:
Sebastian Jack 40 50 60 72 39 67 85 10 92 83
Lick Dan 48 19 2 3 29 10 60 72 83 91
Keng Yao 48 30 68 27 94 81 20 38 90 81
Deck Hao 91 82 65 55 79 93 89 19 23 37
输出到另一个文件是通过添加另一个 int 数字,这是每个学生的平均分数。但是,第二和第四名学生无故被跳过。这是输出:
Sebastian Jack 40 50 60 72 39 67 85 10 92 83 59.8
Keng Yao 48 30 68 27 94 81 20 38 90 81 57.7
这是我的代码:
// This is a program that will output the average quiz score of all student,
based on the student score file
#include <iostream>
#include <fstream>
#include <string>
int main()
{
using namespace std;
ifstream inf;
ofstream outf;
inf.open("infile.txt");
outf.open("outfile.txt");
string name;
int score, total;
double average;
char next;
while (inf >> name) {
total = 0;
outf << name << ' ';
inf >> name;
outf << name << ' ';
while (inf >> score) {
total += score;
outf << score << ' ';
}
average = total / 10.0;
outf << average << endl;
inf.clear();
inf.ignore(numeric_limits<streamsize>::max(), '\n');
}
inf.close();
outf.close();
return 0;
}
我的代码有什么错误吗?非常感谢!
最佳答案
Jaspreet 的建议是正确的;我会添加一些背景知识。
您的程序逻辑是读取数据的一种有效方式。 (另一种可能性,对于面向行的数据“记录”通常更直观,是逐行读取并单独解析每一行,这使得每个“记录”的结尾显而易见。)
现在为什么是ignore()
跳过行?原因是空行在那一点已经被跳过了。详细说明:
您的算法会尝试读取数字,直到失败,因为下一个单词不是数字(或者因为到达了 EOF)。现在读取数字的库逻辑从跳过任何前导空格开始,包括换行符;你现在看到问题了。只有在读取下一个名字的第一个字母后,数字读取算法才会放弃并将读取的字母放回输入中。跳过的换行符不会放回原处。 然后是你的 ignore
并跳过我们站在开头的有效行。
好消息是,对于内部结构指示记录边界的记录(如此处:记录以数字序列结尾;第一个非数字表示新记录的开始),您可以忽略换行符等任何空格并逐字解析。这使程序更加健壮:您可以处理没有空行、多个空行或根本没有任何换行符的数据!
如果数据可以想象地包含一个偶然的错误(比如,数字中的一个字母),换行符仍然可以用作可以尝试重新同步的地方,以实现健壮的编程。但在您的情况下,重新同步会自动发生(可能是在读取了一个解析错误的“记录”后,其名称中包含一个数字)。
作为最后的讨论要点,我建议使用这种类型的数据处理从 stdin 读取并写入 stdout;将数据源和目标留给调用者(通过 myprog < infile.txt > outfile.txt
或类似方式)。 Windows 和 *nix 命令行都支持这一点。这使得程序更加通用并节省了编程工作。如果作业要求读取这两个文件,请将数据的实际算法工作(解析记录并计算平均值)与获取和写入数据分开,后者应该在一个只获取 istream 和 ostream 的函数中。这将使通过字符串流提供来自任何来源的数据成为可能,例如字符串。
事实上,通过定义一个对应于数据记录的类,并重载operator>>
,也可以将解析与算法工作分开。为此,还有一个float record.averageScore()
成员函数:-)。这看起来更像 C++。
以下是一些可能有效的片段。 playerT
是保存数据“记录”的类。我实现了输入函数,因为清除输入流的失败位有点棘手。
/// Currently just a first name, family name and vector of scores
class playerT
{
string firstName;
string lastName;
vector<float> scores;
public:
float averageScore()
{
return scores.size()
? accumulate(scores.begin(), scores.end(), 0.0)/scores.size()
: 0; // avoid dividing by zero.
}
friend ostream & operator<<(ostream &os, const playerT &player);
friend istream &operator>>(istream &is, playerT &player);
};
/// Produces output which could be read back with
/// the input operator. In particular, does not
/// output the average.
ostream &operator<<(ostream &os, const playerT &player)
{
//...
}
// Error handling is left to the caller.
// In particular, after EOF the player must be tested for completeness.
istream &operator>>(istream &is, playerT &player)
{
is >> player.firstName >> player.lastName;
player.scores.clear();
float nextScore;
while(is >> nextScore)
{
player.scores.push_back(nextScore);
}
// Clear the expected format error indicating end of scores
// (and therefore this record),
// but not others which must be handled by the caller.
is.clear(is.rdstate() & ~ios::failbit);
return is;
}
主要功能归结起来相当少,这使得信息流更加清晰。原因是脏 I/O 细节(如清除失败位等)以及计算平均值的“业务逻辑”已移至专用函数。
int main()
{
do // loop to eof
{
playerT newPlayer;
cin >> newPlayer;
// if this was the last record eof is now true
// and the loop will exit after printing the last record.
// todo: handle I/O errors
cout << newPlayer << " (Av.: " << newPlayer.averageScore() << ')' << endl;
}while(cin.good());
return cin.bad(); // eof is ok. only bad is bad.
}
关于无故读取文件时跳过 C++ 行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41398280/