《C++程式設計原理與實踐》第九章習題12答案
阿新 • • 發佈:2018-12-11
《C++程式設計原理與實踐》第九章習題12要改寫Date類,類實現不是用年月日的方法而是用距1970年1月1日的天數來實現。當時感到有點棘手,就搜尋了網上別人的實現方法,我看人實現還是保留了年月日,我覺得這違背了作者的本意。所以還是硬著頭皮自己寫。其實也不難,只是有一個小技巧,就是在如何計算閏年數的方法上。因為現在類裡儲存的是當前日期距離1970年1月1日的天數,所以不能直接用作者計算閏年的函式來實現。因為用1970年作為基準年來實現計算閏年的次數不好實現,所以我想了一個通過曲線手段來實現的方法,具體做法是仍舊從1601年開始算閏年,只是用1601年到當前年的閏年數減去1601年到1970年的閏年數來得到當前年到1970年的閏年數。
// exercise9.12.cpp: 定義控制檯應用程式的入口點。 // // 2018/9/21 因為用1970年作為基準年來實現計算閏年的次數不好實現,所以通過曲線過程實現,具體做法是還是從1601年開始算閏年,用1601年到當前年的閏年數減去1601年到1970年的閏年數就得到當前年到1970年的閏年數。 #include "stdafx.h" #include "..\..\std_lib_facilities.h" #include <iostream> using namespace std; // file Chrono.h namespace Chrono { //------------------------------------------------------------------------------ class Date { public: enum Month { jan = 1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec }; class Invalid { }; // to throw as exception Date(int y, Month m, int d); // check for valid date and initialize Date(); // default constructor // the default copy operations are fine // non-modifying operations: int day() const; Month month() const; int year() const; long get_linearday() const{ return ld; } // modifying operations: void add_day(int n); void add_month(int n); void add_year(int n); private: long ld; //1970.1.1到當前日的天數 }; bool is_date(int y, Date::Month m, int d); // true for valid date bool leapyear(int y); // true if y is a leap year bool operator==(const Date& a, const Date& b); bool operator!=(const Date& a, const Date& b); ostream& operator<<(ostream& os, const Date& d); istream& operator>>(istream& is, Date& dd); const Date& default_date(); } // Chrono //------------------------------------------------------------------------------ //The definitions go into Chrono.cpp: // Chrono.cpp namespace Chrono { // member function definitions: enum Day { // sunday==0 sunday, monday, tuesday, wednesday, thursday, friday, saturday }; const int first_year = 1601;// don't try dates anywhere near this far back in time // the calendar calculation code is brittle the first date // must be the first day of a year divisible by 400 //Januaray 1, 1600 would have been a Monday (had they had the Georgian calendar) const int base_year = 1970; const Date::Month base_month = Date::jan; const int base_day = 1; const int base_day_week = thursday; int nleaps(int); int nmonth(Date::Month); int day_in_year(Date); Date::Date(int yy, Month mm, int dd) { int y = yy - base_year; if (y < 0) error("Date constructor: can't handle days before year 1970"); int m = mm - base_month; int d = dd - base_day; /* If it wasn't for leap years and different lengths of month the answer would be 365*y+31*m+d However, ther real world (the real physical world + conventions) is not that simple. */ if (y == 0 && m == 0) ld = d; // same month else { long days_in_years = 365 * y + nleaps(yy) - nleaps(base_year);//計算當前年與1970年之間的天數,因為函式nleaps是從1601起算閏年,所以要減去1601-1970之間的閏年數。 //實現成員函式month()和day()也一樣考慮 int days_in_months = nmonth(mm); if (feb < mm && leapyear(yy)) ++days_in_months; ld = days_in_years + days_in_months + dd - 1; } if (!is_date(yy, mm, dd)) throw Invalid(); } const Date& default_date(); Date::Date() { ld = default_date().get_linearday(); } int days_in_month(int y, Date::Month m); int days_in_year(int y); int Date::year()const { int y = base_year; long ild = ld; int diy = days_in_year(y); while (ild + 1 > diy) { y++; ild -= diy; diy = days_in_year(y); } return y; } Date::Month Date::month()const { int y = year() - base_year; long days_in_years = 365 * y + nleaps(year()) - nleaps(base_year); long diy = ld - days_in_years; Month m = jan; int dim = days_in_month(year(), m); while (diy + 1 > dim) { m = Month(m + 1); diy -= dim; dim = days_in_month(year(), m); } return m; } int Date::day()const { int y = year() - base_year; long days_in_years = 365 * y + nleaps(year()) - nleaps(base_year); int days_in_months = nmonth(month()); if (Date::feb < month() && leapyear(year())) ++days_in_months; // adjust if we passed a dat added for a leapyear return int(ld - days_in_years - days_in_months + 1); } void Date::add_day(int n) { if (n < 0) error("add_day(): can't handle negative n"); // not yet ld += n; } void Date::add_month(int n) { if (n < 0) error("add_month(): cnot implemented"); // not yet // ... } void Date::add_year(int n) { int y = year(); Month m = month(); int d = day(); if (m == feb && d == 29 && !leapyear(y + n)) { // beware of leap years! // makes sense for both positive and negative n (n==0 should be impossible here) m = mar; // use March 1 instead of February 29 d = 1; } y += n; } //------------------------------------------------------------------------------ // helper functions, etc.: const Date& default_date() { static const Date dd(2001, Date::jan, 1); // start of 21st century return dd; } int days_in_month(int y, Date::Month m) { switch (m) { case Date::feb: // the length of February varies return (leapyear(y)) ? 29 : 28; case Date::apr: case Date::jun: case Date::sep: case Date::nov: return 30; default: return 31; } } bool is_date(int y, Date::Month m, int d) { // assume that y is valid if (m< Date::jan || m>Date::dec) return false; if (d <= 0) return false; // d must be positive if (days_in_month(y, m)<d) return false; return true; } bool leapyear(int y) { // See exercise 9-10 // any year divisible by 4 except centenary years not divisible by 400 if (y % 4) return false; if (y % 100 == 0 && y % 400) return false; return true; } bool operator==(const Date& a, const Date& b) { return a.year() == b.year() && a.month() == b.month() && a.day() == b.day(); } bool operator!=(const Date& a, const Date& b) { return !(a == b); } ostream& operator<<(ostream& os, const Date& d) { return os << '(' << d.year() << ',' << d.month() << ',' << d.day() << ')'; } istream& operator>>(istream& is, Date& dd) { int y, m, d; char ch1, ch2, ch3, ch4; is >> y >> m >> d ; if (!is) { cout << "read fail 1\n"; return is; } /* if (ch1 != '(' || ch2 != ',' || ch3 != ',' || ch4 != ')') { // oops: format error is.clear(ios_base::failbit); // set the fail bit // cout << "read fail 2\n"; return is; } */ dd = Date(y, Date::Month(m), d); // update dd return is; } ostream& operator<<(ostream& os, Day d) // sloppy: I should have used a table { switch (d) { case sunday: os << "Sunday"; break; case monday: os << "Monday"; break; case tuesday: os << "Tuesday"; break; case wednesday: os << "Wednesday"; break; case thursday: os << "Thursday"; break; case friday: os << "Friday"; break; case saturday: os << "Saturday"; break; } return os; } int nmonth(Date::Month m) // number of days before the first day of month #m (january is #1) ignoring leap days { switch (m) { case Date::jan: return 0; case Date::feb: return 31; case Date::mar: return 31 + 28; case Date::apr: return 31 + 28 + 31; case Date::may: return 31 + 28 + 31 + 30; case Date::jun: return 31 + 28 + 31 + 30 + 31; case Date::jul: return 31 + 28 + 31 + 30 + 31 + 30; case Date::aug: return 31 + 28 + 31 + 30 + 31 + 30 + 31; case Date::sep: return 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31; case Date::oct: return 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30; case Date::nov: return 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31; case Date::dec: return 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30; } } int day_in_year(Date a) // e.g. Jan 1 is day #1, Jan 2 is day #1 and feb 3 is day #34 { // int m = nmonth(a.month() - 1); int m = nmonth(a.month()); if (Date::feb<a.month() && leapyear(a.year())) ++m; // adjust if we passed a dat added for a leapyear return m + a.day(); } int days_in_year(int y) { int diy = 365; if (leapyear(y)) diy++; // adjust if we passed a dat added for a leapyear return diy; } int nleaps(int y) // number of leapyears between Jan 1, y and first_year // first_year must be divisible by 400 { const int yy = y - first_year; // const int yy = y - (base_year-1); //base_year-1:based non leap year; return yy / 4 - yy / 100 + yy / 400; // number of leapyears } int linear_day(Date a) // days since default date { int y = a.year() - base_year; if (y<0) error("linear_day: can't handle days before (1601,jan,1)"); int m = a.month() - base_month; int d = a.day() - base_day; /* If it wasn't for leap years and different lengths of month the answer would be 365*y+31*m+d However, ther real world (the real physical world + conventions) is not that simple. */ if (y == 0 && m == 0) return d; // same month int days_in_years = 365 * y + nleaps(a.year()); return days_in_years + day_in_year(a) - 1; } /* Date date_from_linear(int n) // compose the Date (2001,jan,1)+n { return Date(first_date.year(), first_date.month(), first_date.day() + n); // rather limited implementatiuon :-) } */ int operator-(Date a, Date b) // how many days are there between a and b? // if b is earlier than a the answer will be negative { int aval = linear_day(a); int bval = linear_day(b); return aval - bval; } Date operator+(const Date& d, int dd) // dd days beyond d { Date x = d; x.add_day(dd); return x; } /* Note the difference between + and - for Date. It makes sense to subtract two dates, but not to add two dates */ Day day_of_week(const Date& d) // ``just count the days since the start of (our) dates'' { // int x = base_day_week + linear_day(d); int x = base_day_week + d.get_linearday(); return Day(x % 7); // every week is 7 days } Date next_Sunday(const Date& d) { Day dd = day_of_week(d); Date ns = d; ns.add_day(7 - dd); return ns; } Date next_weekday(const Date& d) // assume that Saturday and Sunday are not weekdays { Day dd = day_of_week(d); int n = 1; switch (dd) { case friday: // skip Saturday and Sunday n = 3; break; case saturday: // Skip Sunday n = 2; break; } return d + n; } int week_in_year(const Date& d) // the number of a week in a year is defined by ISO 8601: // week #1 is the week with the year's first Thursday in it // Monday is the first day of the week // 0 means that the date is in the last week ofthe previous year { int diy = day_in_year(d); // jan 1 is day #1 Day jan1 = day_of_week(Date(d.year(), Date::jan, 1)); int week1 = 0; // Jan 1 is in the last week of the previous year int delta; switch (jan1) { // Jan 1 is in the first week of the year case monday: delta = 0; break; case tuesday: delta = 1; break; case wednesday: delta = 2; break; case thursday: delta = 3; break; // Jan 1 is in the last week of the previous year case friday: delta = -3; break; case saturday: delta = -2; break; case sunday: delta = -1; break; } return (diy + delta + 6) / 7; } //------------------------------------------------------------------------------ } // Chrono //------------------------------------------------------------------------------ int zxc; void write(const Chrono::Date& d) // debug output function { cout << d << ": " << day_of_week(d) << "; linear: " << d.get_linearday() << "; of week #" << week_in_year(d) << '\n'; } int main() try { /* Chrono::Date xxx(1971, Chrono::Date::jan, 1); cout << xxx.get_linearday() << endl; write(xxx); Chrono::Date xxx2(1972, Chrono::Date::jan, 1); cout << xxx2.get_linearday() << endl; write(xxx2); Chrono::Date xxx3(1973, Chrono::Date::jan, 1); cout << xxx3.get_linearday() << endl; write(xxx3); */ Chrono::Date xxx(2101, Chrono::Date::jan, 1); // cout << xxx.get_linearday() << endl; write(xxx); Chrono::Date xxxw = next_weekday(xxx); write(xxxw); Chrono::Date xx(2001, Chrono::Date::jan, 8); write(xx); Chrono::Date xxw = next_weekday(xx); write(xxw); Chrono::Date today(2010, Chrono::Date::jan, 16); write(today); Chrono::Date todayw = next_weekday(today); write(todayw); Chrono::Date vote(2010, Chrono::Date::mar, 13); write(vote); Chrono::Date votew = next_weekday(vote); write(votew); Chrono::Date myday(2018, Chrono::Date::Month(8), 24); // cout << day_in_year(myday) << endl; write(myday); using namespace Chrono; cout << "enter some dates: "; Date d; while (cin >> d) { write(d); } // keep_window_open("~"); // For some Windows(tm) setups } catch (Chrono::Date::Invalid&) { cerr << "error: Invalid date\n"; keep_window_open("~"); // For some Windows(tm) setup return 1; } catch (runtime_error& e) { // this code is to produce error messages cout << e.what() << '\n'; keep_window_open("~"); // For some Windows(tm) setups } catch (...) { // this code is to produce error messages cout << "Unknown exception\n"; keep_window_open("~"); // For some Windows(tm) setups }