柚子快報邀請碼778899分享:開發(fā)語言 C++——日期類
柚子快報邀請碼778899分享:開發(fā)語言 C++——日期類
前言:哈嘍小伙伴們,在上一篇文章中我們對C++類與對象的前半段知識進(jìn)行了簡單的分享,其中比較重要的莫過于C++類的六個默認(rèn)成員函數(shù)。
所以這篇文章,我們通過實現(xiàn)一個完整的日期的操作,來對這些成員函數(shù)有一個更加深入的理解。
目錄
一.基本框架
二.日期的比較
三.日期的加減運算
?1.得到月的天數(shù)
2.日期的加運算
3.日期的減運算
4.日期的++--運算
5.日期減日期
6.日期的輸入輸出
7.存在的問題
總結(jié)
一.基本框架
根據(jù)我們過去實現(xiàn)項目的方法,我們需要將聲明與定義分離,同時還要實現(xiàn)測試代碼與源代碼分離,所以我們需要三個文件:
隨后進(jìn)行類的創(chuàng)建,基本成員函數(shù)的實現(xiàn),以及測試代碼的創(chuàng)建等框架。?
隨后進(jìn)行框架的測試:
二.日期的比較
兩個日期之間的比較方式有很多種:>、<、<=、>=、==、!=
這些就需要我們的賦值運算符構(gòu)造函數(shù)出馬了。
上篇文章我們已經(jīng)知道的“>”的寫法:
bool operator>(const Date& d)
{
if (_year > d._year)
return true;
else if (_year == d._year)
{
if (_month > d._month)
return true;
else if (_month == d._month)
return _day > d._day;
else
return false;
}
else
return false;
}
但是就這一個的寫法,就已經(jīng)是很多,很麻煩的一段代碼了,難道像這樣的代碼我們一共要寫6個嗎???當(dāng)然不需要,我們要知道,這些符號之間都有兩兩互補(bǔ)的關(guān)系。
比如說,我們現(xiàn)在寫出了“>”,那么“<=”不就是“>”取反嗎:
bool Date::operator<=(const Date& d)
{
return !(*this > d);
}
?我們建議把“==”給寫出來,因為它比較容易寫:
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
如此一來,“!=”的寫法就會是:
bool Date::operator!=(const Date& d)
{
return !(*this == d);
}
現(xiàn)在我們同時擁有了“>”和“==”,那么將兩者結(jié)合自然就得到了“>=”:
bool Date::operator>=(const Date& d)
{
return *this > d || *this == d;
}
這樣是不是非常的簡潔???其余符號的代碼就不一一列舉啦,詳情請看最后的完整代碼展示。
三.日期的加減運算
日期的加減是一個相對比較困難的運算,它不像數(shù)字的加減那樣簡單,因為不僅存在大小月的天數(shù)不一,而且每四年還會出現(xiàn)閏年的特殊情況,這樣就會導(dǎo)致進(jìn)位非常的麻煩。下面我們就來詳細(xì)分享一下,如此復(fù)雜的日期運算,到底該怎么實現(xiàn)。?
?1.得到月的天數(shù)
首先很重要的一點就是我們要能夠知道每個月都分別有多少天,同時還有2月這個特殊的月份,我們通過一個函數(shù)來實現(xiàn):
int GetMonthdays(int year, int month)
{
assert(month > 0 && month < 13);
int Monthdays[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))
{
return 29;
}
return Monthdays[month - 1];
}
首先要做的就是assert斷言,防止月份輸入錯誤,其次因為閏年是斯四年一次,所以我們默認(rèn)情況下都是平年,通過數(shù)組來記錄,能夠方便獲取。最后就是進(jìn)行閏年二月的判斷,如果2月,我們就去判斷一下是否是閏年。?
2.日期的加運算
我們之間搬出代碼來講:
Date& Date::operator+(int day)
{
_day += day;
while(_day > GetMonthdays(_year, _month))
{
_day -= GetMonthdays(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
依舊是使用賦值運算符構(gòu)造函數(shù),我們直接讓_day加上我們要加的天數(shù),隨后進(jìn)行判斷,如果相加之后的天數(shù)大于當(dāng)月的天數(shù),就讓_day減去該月的天數(shù),剩下的自然就是下個月的天數(shù),同時月份+1,如果月份+1后是13,那就需要向年進(jìn)一,同時月份回到1。
之所以使用循環(huán),是因為如果我要加100天,那向月的進(jìn)位就不止1了,所以要循環(huán)往復(fù)的判斷。
下面我們進(jìn)行測試:
#include"Date.h"
int main()
{
Date d1(2024, 2, 1);
Date d2 = d1 + 30;
d2.Print();
return 0;
}
結(jié)果如下:?
1+30 = 31,而2024年恰巧就是閏年,所以2月有29天,31 - 29 = 2,所以結(jié)果為2024/3/2。?
但是這樣的寫法看似完美,但實際上存在一個很大的錯誤,來看代碼:
#include"Date.h"
int main()
{
Date d1(2024, 2, 1);
Date d2 = d1 + 30;
d2.Print();
d1.Print();
return 0;
}
?我們是讓d2對象去接收d1對象的日期加上20天后的日期,但實際上:
d1對象的日期也發(fā)生了改變。
這個錯誤其實也是可以理解的,因為我們在函數(shù)中直接默認(rèn)進(jìn)行操作的就是d1的成員變量。而這樣的運算,實際上是“+=”運算。
所以想要保證d1的成員變量不變,就必須創(chuàng)建一個臨時變量來代替:
Date Date::operator+(int day)
{
Date tmp(*this);
tmp._day += day;
while (tmp._day > GetMonthdays(tmp._year, tmp._month))
{
tmp._day -= GetMonthdays(tmp._year, tmp._month);
tmp._month++;
if (tmp._month == 13)
{
tmp._year++;
tmp._month = 1;
}
}
return tmp;
}
?創(chuàng)建臨時變量,就用到了我們的拷貝構(gòu)造函數(shù),使用tmp臨時變量代替d1對象進(jìn)行操作。
值得注意的一點是,由于tmp是臨時的變量,當(dāng)這個函數(shù)結(jié)束時就不存在了,所以其作為返回值時,返回類型不能是引用。?
再進(jìn)行測試,結(jié)果如下:
?
3.日期的減運算
理解了加運算之后,減運算的寫法相信小伙伴們都能夠自己悟出來了。
唯一值得注意的是,日期沒有0天:
//日期減等運算
Date& Date::operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_year--;
_month = 12;
}
_day += GetMonthdays(_year, _month);
}
return *this;
}
這里我們先來實現(xiàn)一下“-=”運算,注意while循環(huán)的判斷條件,因為_day不可能等于0。
如果當(dāng)月剩余的天數(shù)不夠用,就需要去借用上個月的天數(shù)繼續(xù)減。結(jié)果如下:
那么問題來了,博主為什么要先實現(xiàn)“-=”呢????
下面我們就來看看“-”運算的實現(xiàn):
//日期減運算
Date Date::operator-(int day)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
怎么樣,有沒有很震驚,為了不改變d1對象,我們確實創(chuàng)建了臨時變量tmp,但是我們大可不必去再寫像上邊那樣的一大長串代碼,因為我們已經(jīng)有“-=”運算了,所以我們直接讓tmp去進(jìn)行“-=”運算,就可以得到結(jié)果:?
而我們前邊實現(xiàn)過的加運算同樣可以借用“+=”運算來寫:
//日期加運算
Date Date::operator+(int day)
{
Date tmp(*this);
tmp += day;
return tmp;
}
4.日期的++--運算
我們知道,“++”和“--”運算都有前置和后置兩種方式,那么我們該怎么用構(gòu)造函數(shù)去分別實現(xiàn)呢?
不管是前置還是后置,它們都會有++,那么我們使用賦值運算符重載函數(shù),函數(shù)名該怎么寫?難道也是一前一后???
并不是,實際上是使用函數(shù)重載來區(qū)分它們:
//前置++運算
Date& operator++();
//后置++運算
Date operator++(int);
對于后置++,給它一個int參數(shù),但是該參數(shù)并不會使用,只是用作編譯器的區(qū)分。
那么兩個函數(shù)又該怎么實現(xiàn)呢????
要注意的是,前置++是先加1,再給值,而后置++是先給值,再++,所以后者就需要一個臨時變量,我們同樣借用一下“+=”函數(shù):
//前置++運算
Date& Date::operator++()
{
*this += 1;
return *this;
}
//后置++運算
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
再來進(jìn)行測試:?
?
如此便可以實現(xiàn)“++”的運算符重載?!?-”與之類似,博主這里就不做展開講解。
5.日期減日期
上邊我們講的日期減運算,是用日期去減去明確的天數(shù)得到一個新的日期。
那么現(xiàn)在如果想用一個日期減去另一個日期,計算兩個日期之間有多少天,又該怎么搞呢???
這個事情看似復(fù)雜,實則代碼寫起來也挺簡單,現(xiàn)在給大家一個思想:
先去比較兩個日期誰大,然后我讓小的一直去++,并計數(shù),直到跟大的相等,計數(shù)的結(jié)果不就是兩者的相差天數(shù)嗎???
//日期-日期
int Date::operator-(const Date& d)
{
Date max = *this;
Date min = d;
int flag = 1;
if (*this < d)
{
flag = -1;
max = d;
min = *this;
}
int n = 0;
while (min < max)
{
min++;
n++;
}
return n * flag;
}
先默認(rèn)前一個值為較大值,后一個為較小值,然后去比較,如果前一個實際上是較小值,則進(jìn)行互換,同時創(chuàng)建一個flag,如果是大-小,結(jié)果即為整數(shù),反之則賦值為-1,結(jié)果為負(fù)數(shù),測試如下:
?
6.日期的輸入輸出
我們前邊講述的日期,都是我們在創(chuàng)建對象時就給定的數(shù)據(jù),輸出時也是用的Print函數(shù)。而且我們知道,cin和cout是無法直接輸入輸出自定義類型的數(shù)據(jù)的。
那現(xiàn)在我們就想先創(chuàng)建一個對象,然后通過cin和cout來輸入輸出數(shù)據(jù),該如何實現(xiàn)呢???
首先我們要知道,cin是istream類型的對象,而cout是ostream類型的對象,那么我們就可以通過賦值運算符重載函數(shù)來重載“>>”和“<<”兩個符號來實現(xiàn):
//日期輸出
void Date::operator<<(ostream& out)
{
out << _year << "年" << _month << "月" << _day << "日" << endl;
}
但是這樣的寫法存在問題:
賦值運算符重載函數(shù)定義在類中作為成員函數(shù)時,其第一個參數(shù)就會是默認(rèn)的隱藏的this參數(shù),也就是d1,而cout則是第二個參數(shù),這就導(dǎo)致我們調(diào)用函數(shù)時兩個實參的順序存在問題,如果將其改為d1< 但是這顯然不符合我們C++的使用規(guī)范,所以想要恢復(fù)順序,就需要將此函數(shù)定義在類外,交換兩個形參的位置: 但是這個時候又出現(xiàn)了問題,因為該函數(shù)在類外,而類的成員變量是私有的,我們不能使用: ? 又該如何解決這個問題呢? 這就需要用到關(guān)鍵字:friend,通過friend,將類外函數(shù)在類內(nèi)進(jìn)行友元聲明,就可以啦: ? 但是此時還有一個問題,在C++中cout是支持同時輸出多個變量的,但是我們定義的函數(shù)卻不行: ? 這是因為按照從左到右的順序,執(zhí)行完cout< //日期輸出 ostream& operator<<(ostream& out, const Date& d) { out << d._year << "年" << d._month << "月" << d._day << "日" << endl; return out; } ?測試如下: 那么知道輸出之后,輸入的寫法就與之類似了: //日期輸入 istream& operator>>(istream& in, Date& d) { in >> d._year >> d._month >> d._day; return in; } 首先就是返回值類型和參數(shù)類型為istream&,其次要注意參數(shù)d不能用const修飾,因為就是要給它輸入值。 測試如下: 7.存在的問題 ?到這里呢,日期類的所有基本功能已經(jīng)全部實現(xiàn)了,但是任然存在一個問題: 我們不小心將2月的天數(shù)傳了個40,這怎么能允許呢,2月最多也就29天,40天怎么可能呢?但是發(fā)現(xiàn)d2還是按部就班的進(jìn)行了“+”運算,這就會出現(xiàn)很大的問題。所以我們需要進(jìn)行傳入檢查。 因為在構(gòu)造函數(shù)和輸入函數(shù)中都需要進(jìn)行檢查,所以我們需要一個創(chuàng)建一個函數(shù): //檢查日期合法性 bool Date::CheckInvalid() { if (_year <= 0 || _month < 1 || _month > 12 || _day < 1 || _day > GetMonthdays(_year, _month)) return false; else return true; } ?分別判斷年,月,日是否都合法。 //初始化 Date::Date(int year, int month, int day) { _year = year; _month = month; _day = day; if (!CheckInvalid()) { cout << *this << "該日期非法" << endl; exit(-1); } } 構(gòu)造函數(shù)中使用,若非法直接結(jié)束程序: //日期輸入 istream& operator>>(istream& in, Date& d) { while(1) { in >> d._year >> d._month >> d._day; if (!d.CheckInvalid()) cout << "輸入的日期非法,請重新輸入:" << endl; else break; } return in; } 輸入函數(shù)中使用,若非法則重新輸入:? ? 總結(jié) 日期類的實現(xiàn)到這里就分享完啦,希望能夠幫助小伙伴們更加深入的理解類的內(nèi)部結(jié)構(gòu)及其成員函數(shù)的操作實現(xiàn)。 喜歡博主文章的小伙伴記得一鍵三連哦,我們下期再見! 柚子快報邀請碼778899分享:開發(fā)語言 C++——日期類 參考閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。