【C++】日期计算器
文章目錄
- 一、前言
- 二、日期類的實(shí)現(xiàn)
- 1.Date類中默認(rèn)成員函數(shù)的使用
- 1.構(gòu)造函數(shù)
- 2.析構(gòu)函數(shù)
- 3.拷貝構(gòu)造函數(shù)
- 4.賦值運(yùn)算符重載
- 5.const成員函數(shù)
- 6.取地址操作符重載和const取地址操作符重載
- 2.檢查日期的合法性
- 3.運(yùn)算符重載
- < 運(yùn)算符重載、== 運(yùn)算符重載
- <= 運(yùn)算符重載、> 運(yùn)算符重載
- >= 運(yùn)算符重載、!= 運(yùn)算符重載
- 4.改進(jìn)和優(yōu)化
- 5.日期操作
- 日期 + 天數(shù)、日期 += 天數(shù)
- 日期 -= 天數(shù)、日期 - 天數(shù)
- 前置 ++、后置 ++
- 前置 --、后置 --
- 日期 - 日期
- 6.<<流插入、>>流提取運(yùn)算符的使用
- 三、總結(jié)
一、前言
在我們的日常生活中,我們可能需要計(jì)算幾天后的日期,或計(jì)算日期差等,現(xiàn)如今計(jì)算日期的方式有很多,簡單粗暴的直接查看日歷,快捷點(diǎn)的直接使用日期計(jì)算器來求得,先給一個網(wǎng)絡(luò)上的日期計(jì)算器截圖:
現(xiàn)在,就讓我們用代碼來實(shí)現(xiàn)其工作原理吧。
- 注意:本篇日期類**.h文件放聲明,.cpp**文件放定義
二、日期類的實(shí)現(xiàn)
1.Date類中默認(rèn)成員函數(shù)的使用
1.構(gòu)造函數(shù)
//構(gòu)造函數(shù) - 可寫可不寫 Date::Date(int year, int month, int day) {if (year >= 1 && month <= 12 && month >= 1 && day <= GetMonthDay(year, month)){_year = year;_month = month;_day = day;}elsecout << "日期非法" << endl; }日期類的構(gòu)造函數(shù)需要對日期的合法性進(jìn)行判斷。
2.析構(gòu)函數(shù)
//析構(gòu)函數(shù) - 可寫可不寫 Date::~Date() {; }日期類并沒有申請資源(動態(tài)開辟內(nèi)存,打開文件),所以這里我們寫不寫都可以,系統(tǒng)會默認(rèn)生成。
3.拷貝構(gòu)造函數(shù)
//拷貝構(gòu)造函數(shù) - 可寫可不寫 Date::Date(const Date& d) {_year = d._year;_month = d._month;_day = d._day; }系統(tǒng)默認(rèn)生成的拷貝構(gòu)造函數(shù)會對內(nèi)置類型進(jìn)行淺拷貝,所以我們也不用寫,但是如果有有資源的對象時,需要深拷貝。
4.賦值運(yùn)算符重載
//賦值運(yùn)算符重載 - 可寫可不寫 Date& Date::operator=(const Date& d) {_year = d._year;_month = d._month;_day = d._day;return *this; }也可不寫,使用系統(tǒng)默認(rèn)生成的即可。拷貝構(gòu)造和賦值運(yùn)算符重載的區(qū)別在于拷貝構(gòu)造用于對象構(gòu)造時使用,而賦值運(yùn)算符重載用于已存在對象賦值時使用。后續(xù)處理有資源的對象時,需要先把舊空間釋放,再開一塊同樣大小的空間,進(jìn)行數(shù)據(jù)拷貝。
5.const成員函數(shù)
//其中的例子 //日期 - 天數(shù) Date Date::operator-(int day) const {Date ret(*this);ret -= day;return ret; }下面有很多函數(shù)都用到了const修飾,這是因?yàn)槌蓡T函數(shù)默認(rèn)第一個參數(shù)為 Date* const this,而const Date* 指向的內(nèi)容不能被修改,可是當(dāng)它傳給Date* 時就出錯了,因?yàn)镈ate* 是可以修改的,這里傳過去會導(dǎo)致權(quán)限放大。加上const去保護(hù)this指向的內(nèi)容,也就是在函數(shù)的后面加上const。
6.取地址操作符重載和const取地址操作符重載
//取地址操作符重載 Date* Date::operator&() {return this;//return nullptr; }//const取地址操作符重載 const Date* Date::operator&()const {return this;//return nullptr; }不用自己寫,除非想讓別人通過取地址操作符獲取到特定值(自己在重載函數(shù)內(nèi)部寫)或屏蔽類地址。
2.檢查日期的合法性
實(shí)現(xiàn)日期類首先就得檢查日期的合法性,這其中就包括大小月,閏年的2月有29天,一年只有12個月等等細(xì)節(jié)都要考慮到。
class Date { public:bool isLeapYear(int year) //判斷是否為閏年{//四年一閏百年不閏或四百年一閏return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);}int GetMonthDay(int year, int month){//加上static防止函數(shù)頻繁調(diào)用開辟幾十個字節(jié)大小的數(shù)組,最好加上static int monthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month == 2 && isLeapYear(year))return 29; //閏年2月29天elsereturn monthDayArray[month];}Date(int year = 1, int month = 1, int day = 1){//這里我們不考慮公元前if (year >= 1 && month <= 12 && month >= 1 && day <= GetMonthDay(year, month)){//確保日期合法_year = year;_month = month;_day = day;}} private:int _year;int _month;int _day; };3.運(yùn)算符重載
< 運(yùn)算符重載、== 運(yùn)算符重載
- 思路:
< 運(yùn)算符重載在我上一篇博文已經(jīng)詳細(xì)講解過,主要是先把大于的情況全部統(tǒng)計(jì)出來,就比如我要比較實(shí)例化對象d1是否小于實(shí)例化對象d2,只需考慮如下三種滿足的情況:
- d1的年小于d2的年
- d1與d2年相等,d1的月小于d2的月
- d1與d2年相等月相等,d1的天小于d2的天
這三種全是小于的情況,返回true,其余返回false
- 代碼如下:
- 思路:
== 運(yùn)算符重載其實(shí)非常簡單,只需要判斷d1和d2的年、月、天是否對應(yīng)相等即可:
- 代碼如下:
<= 運(yùn)算符重載、> 運(yùn)算符重載
- 思路: 復(fù)用
<= 的運(yùn)算符重載,這里要仔細(xì)想一想 <= 成立的條件是啥。不就是 要么 < 要么 = 嗎?我們只需要復(fù)用先前寫的 < 運(yùn)算符重載和 <=運(yùn)算符重載,無需自己耗費(fèi)精力寫。
- 代碼如下:
- 思路: 復(fù)用
> 的反義就是 <=,所以我們只需要復(fù)用 <= 運(yùn)算符重載,再對其取反即可解決此問題。
- 代碼如下:
>= 運(yùn)算符重載、!= 運(yùn)算符重載
- 思路: 復(fù)用
>= 的反義就是 <,所以我們只需要復(fù)用 < 運(yùn)算符重載,再對其取反即可。
- 代碼如下:
- 思路: 復(fù)用
有了前面的基礎(chǔ),寫個 != 也很簡單,對 == 取反即可
- 代碼如下:
4.改進(jìn)和優(yōu)化
上述我們寫的運(yùn)算符重載都是建立在聲明定義分離的,這里我們可以對其進(jìn)行優(yōu)化,如下:
先前我們學(xué)過內(nèi)聯(lián),可以幫助我們對于短小函數(shù)減少函數(shù)調(diào)用而引發(fā)的效率損失問題,因此我們可以把上述幾個運(yùn)算符重載函數(shù)放成內(nèi)聯(lián),此外,有一種簡單粗暴的方法:直接在類里定義,因?yàn)轭惱锏暮瘮?shù)默認(rèn)內(nèi)聯(lián),還省的我們自己寫inline,而且我們也不用在類外加上類域了,當(dāng)然,有些長的函數(shù)還是聲明和定義分離比較好。
- Date.h 文件:
- Date.cpp 文件:
5.日期操作
日期 + 天數(shù)、日期 += 天數(shù)
- 思路:
對于日期 + 天數(shù),我們得到的還是一個日期。特別需要注意進(jìn)位的問題(天滿了往月進(jìn),月滿了往年進(jìn)),主要考慮如下幾個特殊點(diǎn):
- 加過的天數(shù)超過該月的最大天數(shù),需要進(jìn)位
- 當(dāng)月進(jìn)位到13時,年進(jìn)位+1,月置為1
- 法一:
出了作用域,對象ret不在,它是一個局部對象,我們這里不能用引用,用了的話,返回的就是ret的別名,但是ret又已經(jīng)銷毀了,訪問野指針了,所以出了作用域,如果對象不在了,就不能用引用返回,要用傳值返回
- 法二:復(fù)用日期+=天數(shù)
此法是建立在日期+=天數(shù)的基礎(chǔ)上完成的,這里各位可以先看下文日期+=天數(shù),然后我們進(jìn)行復(fù)用:
Date Date::operator+(int day) const {//法二:復(fù)用日期 += 天數(shù)Date ret(*this);ret += day;return ret; }- 法一和法二熟優(yōu)?
答案:法二更好,也就是用+去復(fù)用+=,具體原因在下文會解釋。
這里實(shí)現(xiàn) += 其實(shí)有兩種方案
- 法一:
前面我實(shí)現(xiàn)的日期+天數(shù),仔細(xì)觀察我的代碼,函數(shù)的第一行,我就調(diào)用了一個拷貝構(gòu)造:
Date ret(*this); //拷貝構(gòu)造,拿d1去初始化ret這里調(diào)用拷貝構(gòu)造,是為了不在* this本身上做變動,只在ret上進(jìn)行操作,其理由是日期+天數(shù)得到的是另一個日期,而不用拷貝構(gòu)造直接在*this上做改動只會導(dǎo)致原有的日期也變化,而這個變化正是我日期 += 天數(shù)的需求。
仔細(xì)想想:+=天數(shù)就是在原有的日期上再加一定的天數(shù),直接對*this做手腳即可,因此只需對日期+天數(shù)的代碼進(jìn)行小改動即可:
Date& Date::operator+=(int day) //傳引用返回 {//如果day小于0,要單獨(dú)處理if (day < 0){return *this -= -day;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){_year++;_month = 1;}}return *this; }注意這里是傳引用返回,原因就在于我返回的*this是全局的,出了作用域還在
- 法二:復(fù)用日期 +天數(shù)
- 法一和法二熟優(yōu)?
答案:法一。其實(shí)討論這個問題就是在討論用+去復(fù)用+=號還是用+=復(fù)用+號,答案是用+去復(fù)用+=好,因?yàn)?#43;有兩次拷貝,而+=沒有拷貝,所以實(shí)現(xiàn)+=,并且用+去復(fù)用+=效率更高
日期 -= 天數(shù)、日期 - 天數(shù)
- 思路:
日期-=天數(shù)得到的還是一個日期,且是在原日期的基礎(chǔ)上做改動。合法的日期減去天數(shù)后的day只要>0就沒問題,若小于0就要借位了。要注意當(dāng)減去的天數(shù)<0時單獨(dú)討論。具體步驟如下:
- 代碼如下:
有了先前日期+和+=的基礎(chǔ),這里實(shí)現(xiàn)日期 - 天數(shù)直接復(fù)用日期 -= 天數(shù)即可:
//日期 - 天數(shù) Date Date::operator-(int day) const {Date ret(*this);ret -= day;return ret; }前置 ++、后置 ++
- 思路:
C++里有前置++和后置++,這就導(dǎo)致一個巨大的問題,該如何區(qū)分它們,具體實(shí)現(xiàn)過程不難(直接復(fù)用+=即可),難的是如何區(qū)分前置和后置。因此C++規(guī)定,無參的為前置,有參的為后置。
- 代碼如下:
- 思路:
有參的即為后置,后置++拿到的返回值應(yīng)該是自己本身未加過的,因此要先把自己保存起來,再++*this,隨后返回自己。
- 代碼如下:
前置 --、后置 –
- 思路:
前置–和前置++沒啥區(qū)別,只不過內(nèi)部復(fù)用的是-=
- 代碼如下:
- 思路:
后置–和后置++類似,只不過內(nèi)部復(fù)用的是-=,不再贅述
- 代碼如下:
日期 - 日期
- 思路:
日期 - 日期得到的是天數(shù),首先我們得判斷兩個日期的大小,用min和max代替小的和大的,隨后,算出min和max之間的差距,若min!=max,則min就++,隨即定義變量n也自增++,最后返回n(注意符號)
- 代碼如下:
6.<<流插入、>>流提取運(yùn)算符的使用
- 思路:
這里我們重載<<操作符,運(yùn)算符重載是有要求的,這里的d1必須是左操作數(shù),那么cout一定是右操作數(shù)。那么我們只能使用如下方式調(diào)用。
void Date::operator<<(ostream& out) {cout << _year << "年" << _month << "月" << _day << "日" << endl; } int main() {Date d1, d2;d1 << cout; // d1.operator << (cout); }我們寫成成員函數(shù),就只能這樣寫,因?yàn)殡[藏的this指針默認(rèn)搶了第一個參數(shù)位置。日期類對象就是左操作數(shù),不符合使用習(xí)慣和可讀性。
那么我們采取其他的辦法,既然成員函數(shù)就只能這么寫,那我們把它變成全局函數(shù),寫在類外面,這時候我們就可以自己控制參數(shù)的順序。
//operator(cout, d1) cout << d1; void operator<<(ostream& out, const Date& d) {cout << d._year << "年" << d._month << "月" << d._day << "日" << endl; }但是這里又會有一個大問題,我們不能訪問私有的成員變量?那我們把成員變量改成公有試一試?
這里其實(shí)會出現(xiàn)報錯。這是因?yàn)檫@個函數(shù)是全局函數(shù),在Date.cpp、Test.cpp預(yù)處理的時候會被展開,造成多重定義的沖突問題,只要是全局函數(shù)/全局變量,都會出現(xiàn)這樣的問題。那我們怎么解決?
可能大家都知道應(yīng)該聲明與定義分離。
這里我們還可以用另一個方法,把它變成靜態(tài)成員函數(shù)。
為什么這樣就不報錯呢?我們回憶一下static的作用,能夠改變變量/函數(shù)的生命周期。但是它還有另一個作用,會改變變量/函數(shù)的鏈接屬性:只在當(dāng)前文件可見,就不用進(jìn)符號表,告知編譯器我當(dāng)前文件用就可以了。
那我們這里還有沒有什么問題呢?我們賦值的時候有時候會鏈?zhǔn)秸{(diào)用,這時候就會出問題。
因?yàn)?cout << d1先結(jié)合,但是并沒有返回值,這里應(yīng)該有一個cout的返回值。這里我們更改一下。
這里我們其實(shí)還可以再優(yōu)化一下,直接把它變成內(nèi)聯(lián)函數(shù),不僅解決頻繁調(diào)用效率低的問題,還能解決沖突問題。
因?yàn)閮?nèi)聯(lián)函數(shù)在用的時候直接展開,也不用進(jìn)符號表。
但是我不想讓成員變成公有,能不能改變呢?
這里同樣有兩種方式:
1.創(chuàng)建一個公有的成員函數(shù)getYear,getMonth,getday,去訪問私有成員變量,java很喜歡用這種方式。
2.調(diào)用友元,允許你訪問私有的成員變量。
三、總結(jié)
本篇日期類把先前學(xué)到的引用,傳值/傳引用返回、拷貝構(gòu)造、復(fù)用等等知識點(diǎn)柔和到了一起,非常值得大家操手練習(xí)練習(xí),創(chuàng)作不易,還望三連。
- 日期類的源碼鏈接:日期計(jì)算器
總結(jié)
以上是生活随笔為你收集整理的【C++】日期计算器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据库三级封锁协议
- 下一篇: getchar();吸收回车符