好的,即使没有看到您的完整源代码,我也会帮助您。
你有两个主要问题
- 您不检查提取操作 (>>) 是否失败
- 在没有
std::getline 的情况下提取后,您的endline 字符有问题
那么,会发生什么。假设如下简单的测试程序
#include <iostream>
#include <sstream>
std::istringstream testDataStream{ R"(1 2
string1
3 4
string2)" };
int main() {
int i1{}, i2{}, i3{}, i4{};
std::string s1{}, s2{};
testDataStream >> i1 >> i2;
std::getline(testDataStream, s1);
testDataStream >> i3 >> i4;
std::getline(testDataStream, s2);
// more code . . .
return 0;
}
首先我们将读取 i1 和 i2 的正确信息。提取器运算符一直读取到下一个空格,但不使用行尾的 '\n'。它已读取 i1 和 i2,跳过空白。然后停下来。没有更多的活动。什么都不会消耗 '\n'。
如果您现在调用std::getline,它将在值 2 之后开始读取并找到一个“\n”。然后它停止读取并返回一个空字符串。因此,在调用std::getline 之后,std::string s1 将为空,'\n' 将被消耗,并且下一个要读取的位置是“string1”。
现在我们试试testDataStream >> i3 >> i4;。但是没有 3 和 4。记住:要读取的位置仍然在“string1”。因此提取操作将失败,并且将设置流的failbit。
但是您从不测试failbit,因此程序的其余部分会静默失败。
您可以并且应该检查每个提取操作的结果。
这可以简单地完成
if (testDataStream >> i1 >> i2) {
// Good. Continue standard operation
}
else {
// Bad. Show error message and do error handling
}
为什么会这样?链式提取操作在最后返回对流的引用。并且流有一个重载的布尔运算符。请参阅here 和here。
因此,要测试流的状态,您总是可以简单地编写 if (someStream)。
好的,明白了。但是我们怎样才能摆脱字符串末尾的尾随'\n'。为此,我们有函数ignore。请参阅链接中的文档。
要修复avoe代码sn-p我们需要添加ignore 2次:
#include <limits>
#include <iostream>
#include <sstream>
std::istringstream testDataStream{ R"(1 2
string1
3 4
string2)" };
int main() {
int i1{}, i2{}, i3{}, i4{};
std::string s1{}, s2{};
testDataStream >> i1 >> i2;
testDataStream.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::getline(testDataStream, s1);
testDataStream >> i3 >> i4;
testDataStream.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::getline(testDataStream, s2);
// more code . . .
return 0;
}
为了向您展示如何实现这一切,我为您准备了一个完整的工作示例。
请看:
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <iterator>
#include <limits>
#include <array>
// Function to check, if we have a valid month
constexpr std::array<std::string_view, 12> Months{ "January", "February", "March","April", "May", "June", "July","August", "September", "October","November", "December" };
bool isInValidMonth(const std::string& m) { return std::find(Months.begin(), Months.end(), m) == Months.end(); }
struct Unit {
// Data for one semester/class unit for a student
std::string id{};
std::string name{};
unsigned int credit{};
unsigned int mark{};
unsigned int dateDay{};
std::string dateMonth{};
unsigned int dateYear{};
// This will check, if the data that we have read is ok, and if not, set the fail bit of the stream
std::istream& sanityCheck(std::istream& is) {
// Check for non plausible data. If there is unplausible data anywhere
if (!is || credit > 500 || mark > 100 || dateDay == 0U || dateDay > 31 || dateYear < 1920 || dateYear > 2100 || isInValidMonth(dateMonth)) {
// Set the streams failbit
is.setstate(std::ios::failbit);
std::cerr << "\n*** Error: Problem while reading unit data\n";
}
return is;
}
// Overide extractor. Get Unit data
friend std::istream& operator >> (std::istream& is, Unit& u)
{
if (std::getline(std::getline(is, u.name), u.id) >> u.credit >> u.mark >> u.dateDay >> u.dateMonth >> u.dateYear)
// Eat up the trailing '\n'
is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return u.sanityCheck(is);
}
// Overwrite inserter. Write unit data
friend std::ostream& operator << (std::ostream& os, const Unit& u) {
return os << "\tUnit ID:\t" << u.id << "\n\tUnit name:\t" << u.name << "\n\tCredits:\t" << u.credit << "\n\tMarks:\t\t"
<< u.mark << "\n\tDate:\t\t" << u.dateDay << " " << u.dateMonth << " " << u.dateYear << "\n";
}
};
struct Student {
// Student data
unsigned long id{};
unsigned long semester{};
std::vector<Unit> unit{};
// Override extractor: Read Student Data
friend std::istream& operator >> (std::istream& is, Student& s) {
s.unit.clear();
// Here will store the number of units that we need to read
size_t numberOfUnits{};
// Read, id and semester and numberOfUnits and check, if that was successfull
if (is >> s.id >> s.semester >> numberOfUnits) {
// Eat up the trailing '\n'
is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
// Read all units. Call the extractor operator n times, but only if everything OK
for (size_t i = 0U; i < numberOfUnits && is; ++i) {
// Read a unit and check, if OK
if (Unit temp{}; is >> temp) {
// If OK, append the new unit to the student
s.unit.push_back(std::move(temp));
}
}
}
// Sanity check. We expect to have numberOfUnits
if (!is && s.unit.size() != numberOfUnits) {
// Set the streams failbit
is.setstate(std::ios::failbit);
std::cerr << "\n*** Error: Problem while reading student data\n";
}
return is;
}
// Override inserter: Write student data
friend std::ostream& operator << (std::ostream& os, const Student& s) {
os << "\n\nStudent ID:\t" << s.id << "\nSemester:\t" << s.semester << "\n\n";
std::copy(s.unit.begin(), s.unit.end(), std::ostream_iterator<Unit>(os, "\n"));
return os;
}
};
struct Register {
std::vector<Student> students{};
// Override extractor: Read Student Data
friend std::istream& operator >> (std::istream& is, Register& r) {
for (Student s{}; is && is >> s; ) if (is) r.students.push_back(s);
return is;
}
// Override inserter: Write complete Register
friend std::ostream& operator << (std::ostream& os, const Register& r) {
std::copy(r.students.begin(),r.students.end(), std::ostream_iterator<Student>(os, "\n"));
return os;
}
};
std::istringstream inputStream{ R"(102234 962 3
Data Structures and Abstractions
ICT283
3 90 30 June 2016
Applied ICT Research Skills
BSC250
3 92 24 April 1993
Games Technology
ICT292
3 76 4 August 1998
222222 22 2
Data Structures and Abstractions 2
ICT222
3 90 30 June 2016
Applied ICT Research Skills 2
BSC222
2 2 2 February 1993
)" };
int main() {
Register r;
inputStream >> r;
std::cout << r;
return 0;
}
玩得开心。 . .