#include<iostream>
#include<fstream>
#include<vector>
#include<string>
using namespace std;
constexpr int not_a_reading = -7777;
constexpr int not_a_month = -1;
constexpr int implausible_min = -200;
constexpr int implausible_max = 200;
vector<string> month_tbl = { "january", "febuary", "march", "april","may","june","july","august",
"september","october","november","december" };
struct Day
{
//made a day struct containing a day vec which has 24 elements(24 hours)
//and each element contains the temperature at that hour.
vector<int> hour = vector<int>(24, not_a_reading);
};
struct Month
{
int month = not_a_month;
vector<Day> day{ 32 };
//throwing away day[0]
};
struct Year
{
int year;
vector<Month> month{ 12 };
};
struct Reading { //just for reading data.
int day;
int hour;
double temperature;
};
int month_to_int(string m)
{
for (int i = 0; i < month_tbl.size(); i )
{
if (month_tbl[i] == m) return i 1;
}
return -1; //problem
}
bool is_valid(const Reading& r)
// a rough test
{
if (r.day < 1 || 31 < r.day) return false;
if (r.hour < 0 || 23 < r.hour) return false;
if (r.temperature < implausible_min || implausible_max < r.temperature)
return false;
return true;
}
void end_of_loop(istream& ist, char term, const string& message)
{
if (ist.fail()) { // use term as terminator and/or separator
ist.clear();
char ch;
if (ist >> ch && ch == term) return; // all is fine
throw runtime_error(message);
}
}
//overloading insertion operator(>>)
istream& operator>>(istream& is, Reading& r)
// read a temperature reading from is into r
// format: ( 3 4 9.7 )
// check format, but don’t bother with data validity
{
char ch1;
if (is >> ch1 && ch1 != '(') { // could it be a Reading?
is.unget();
is.clear(ios_base::failbit); //set stream to fail state
return is;
}
char ch2;
int d;
int h;
double t;
is >> d >> h >> t >> ch2;
if (!is || ch2 != ')') throw runtime_error("bad reading"); // messed-up reading
r.day = d;
r.hour = h;
r.temperature = t;
return is;
}
istream& operator>>(istream& is, Month& m)
// read a month from is into m
// format: { month feb . . . }
{
char ch = 0;
if (is >> ch && ch != '{') {
is.unget();
is.clear(ios_base::failbit); // we failed to read a Month
return is;
}
//we got a {
string month_marker;
string mm;
is >> month_marker >> mm;
if (!is || month_marker != "month") throw runtime_error("bad start of month");
m.month = month_to_int(mm);
int duplicates = 0;
int invalids = 0;
for (Reading r; is >> r; ) {
if (is_valid(r)) {
if (m.day[r.day].hour[r.hour] != not_a_reading)
duplicates;
m.day[r.day].hour[r.hour] = r.temperature;
}
else
invalids;
}
if (invalids) throw runtime_error("invalid readings in month");
if (duplicates) throw runtime_error("duplicate readings in month");
end_of_loop(is, '}', "bad end of month");
return is;
}
istream& operator>>(istream& is, Year& y)
// read a year from is into y
// format: { year 1972 . . . }
{
char ch;
is >> ch;
if (ch != '{') {
is.unget();
is.clear(ios::failbit);
return is;
}
//awesome it was {
string year_marker;
int yy;
is >> year_marker >> yy;
if (!is || year_marker != "year") throw runtime_error("bad start of year");
y.year = yy;
while (true) {
Month m; // get a clean m each time around
if (!(is >> m)) break;
y.month[m.month] = m;
}
end_of_loop(is, '}', "bad end of year");
return is;
}
int main()
{
Reading r;
ifstream ist("justtext.txt");
ist >> r;
cout << r.day << '\n';
cout << r.hour << '\n';
cout << r.temperature << '\n';
}
We would have preferred “boringly similar” to just “similar,” but there is a significant difference. Have a look at the read loop. Did you expect something like the following?
for (Month m; is >> m; )
> y.month[m.month] = m;
You probably should have, because that’s the way we have written all the read loops so far. That’s actually what we first wrote, and it’s wrong. The problem is that operator>>(istream& is, Month& m) doesn’t assign a brand-new value to m; it simply adds data from Readings to m. Thus, the repeated is>>m would have kept adding to our one and only m. Oops! Each new month would have gotten all the readings from all previous months of that year. We need a brand-new, clean Month to read into each time we do is>>m. The easiest way to do that was to put the definition of m inside the loop so that it would be initialized each time around.
I just don't understand how what he's saying here is true? He says is>>m doesn't assign a brand new value to m but if I look at the overloaded operator for months it seems that it does? Can anyone explain this to me maybe I'm just missing something because I'm tired.
When he says this he's talking about the code in the overloaded >> operator for year.
while(true) {
Month m; // get a clean m each time around
if(!(is >> m)) break;
y.month[m.month] = m;
}
CodePudding user response:
The problem is that in
for (Month m; is >> m; ) ...
the variable m
isn't recreated each and every iteration. It's more like this:
{
Month m;
while (is >> m)
{
...
}
}
If the stream extract operator >>
function overload doesn't "clear" the Month
object, then the contents of m
will just build up to unexpected values.
With that said, the stream extract operator should "clear" the previous contents of the object it's writing to, it's the expected behavior. And if it doesn't then the real problem is in the istream& operator>>(istream& is, Month& m)
function.
CodePudding user response:
The original author talks about the different semantics of the insertion operator >>
with regard to the different classes.
For Reading
the operator simply overwrites everything that is already present in the reading, so you can reuse the same variable over and over again. This allows for declaring the variable only once in the for
statement and then keep reusing it.
For Month
however the >>
just adds new information to the already present information. Therefore you can not reuse the same variable and instead have to declare a local variable inside the scope of the loop.