C++日期類的實(shí)現(xiàn)日期計(jì)算器舉例詳解
前言
1.日期類是一種十分經(jīng)典的類型。對(duì)于C++的初學(xué)者,它能夠幫助我們?nèi)跁?huì)貫通許多C++的基礎(chǔ)知識(shí),它涉及許多的基礎(chǔ)語(yǔ)法,比如引用,函數(shù)重載,傳值/傳參返回,構(gòu)造函數(shù),運(yùn)算符重載,const成員等等。
如果有不了解的,可以前往我的主頁(yè)瀏覽相關(guān)文章。
日期計(jì)算器可以實(shí)現(xiàn)兩個(gè)日期的比較,兩個(gè)日期的相減,日期的加減天數(shù)等有意義的運(yùn)算。
2.本文依然采用多文件方式。其中:
Date.h //定義類,存放各函數(shù)的聲明;Date.cpp //實(shí)現(xiàn)各重載函數(shù);Test.cpp //測(cè)試各函數(shù)的功能。
在C++中,由于函數(shù)的聲明與定義分離,如果要定義成員函數(shù),就要指定類域,這是基本語(yǔ)法。
一,各個(gè)函數(shù)功能的實(shí)現(xiàn)
1. 檢查輸入的日期是否合法
不管是日期的比較還是日期的運(yùn)算,第一步都要檢查日期的合法性。特別是月份和每個(gè)月的天數(shù)。
代碼實(shí)現(xiàn)如下:
bool Date::CheakDate() { if (_month < 1 || _month>12 || _day<1 || _day>GetMonthDay(_year, _month)) { return false; } else { return true; } }
2. 構(gòu)造函數(shù) (初始化函數(shù))
為了方便,在使用默認(rèn)構(gòu)造函數(shù)時(shí),一般是自己顯式的實(shí)現(xiàn)一個(gè)全缺省構(gòu)造函數(shù)。
注意:在函數(shù)的聲明和定義分離時(shí),如果要給缺省值,必須在函數(shù)聲明的時(shí)候給。
代碼實(shí)現(xiàn)如下:
Date::Date(int year, int month, int day) { _year = year; _month = month; _day = day; //日期的源頭,是從構(gòu)造函數(shù)里出來的,所以要在這里判斷 if (!CheakDate()) { cout << "日期非法!" << endl; } }
二,比較類的運(yùn)算符重載
3. <運(yùn)算符重載
判斷兩個(gè)日期誰(shuí)更小。思路:先比年,年小就小,年相等比月,月小就小,年月相等比日,日小就小。
代碼實(shí)現(xiàn)如下:
d1 < d2 隱含的this指針是d1,d是d2的別名。
bool Date::operator< (const Date& d) const { 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; } } return false; }
4. ==運(yùn)算符重載
判斷兩個(gè)日期是否相等 。這個(gè)比較簡(jiǎn)單,如果兩者的年月日都相等,即相等。
代碼實(shí)現(xiàn)如下:
bool Date::operator==(const Date& d) const { return _year == d._year && _month == d._month && _day == d._day; }
5. >=運(yùn)算符重載
有人可能會(huì)仿照<運(yùn)算符重載的方法,使用復(fù)雜的邏輯,寫各種晦澀的代碼實(shí)現(xiàn)。其實(shí)只要實(shí)現(xiàn)了<運(yùn)算符重載和==運(yùn)算符重載,下面的日期比較類都是可以復(fù)用的。 比如這里的>=,< 取反就是>=。
代碼實(shí)現(xiàn)如下:
bool Date::operator>= (const Date& d) const { return !(*this < d); }
6. >運(yùn)算符重載
<= 取反,就是>。
bool Date::operator> (const Date& d) const { return !(*this <= d); }
7. <=運(yùn)算符重載
只要滿足<或者=,就是<=。
bool Date::operator<= (const Date& d) const { return *this < d || *this == d; }
8. !=運(yùn)算符重載
==去取反,就是!=。
bool Date::operator!=(const Date& d) const { return !(*this == d); }
9. 獲取某月的天數(shù)
這個(gè)函數(shù)是整個(gè)日期類的關(guān)鍵,也是最頻繁調(diào)用的一個(gè)函數(shù)。由于這個(gè)原因,最好把它定義成內(nèi)聯(lián)函數(shù),避免每次調(diào)用都要開辟空間,可以提升效率。根據(jù)C++的語(yǔ)法,定義在類里默認(rèn)是內(nèi)聯(lián),inline可加可不加。
代碼實(shí)現(xiàn)如下:
這里還有兩個(gè)優(yōu)化的細(xì)節(jié):
1. month == 2 和后面的取模運(yùn)算的位置。首先滿足是2月,再判斷是否是閏年,效率會(huì)更高。2. static的使用,由于該函數(shù)頻繁調(diào)用,把數(shù)組放在靜態(tài)區(qū),避免每次調(diào)用函數(shù)時(shí)每次都要開辟數(shù)組空間。
int GetMonthDay(int year, int month) { //斷言,確保輸入月份的有效性 assert(month > 0 && month < 13); //枚舉出月份的天數(shù) static int monthDayArray[13] = { -1, 31,28,31,30,31,30,31,31,30,31,30,31 }; //判斷2月的平年和閏年 if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) { return 29; } else { return monthDayArray[month]; } }
三,運(yùn)算類的重載
10. 日期+=天數(shù)
比如d1 + 50,這里的d1已經(jīng)改變了。
計(jì)算過程如下:
注意,每次超過月份天數(shù)后,是減當(dāng)前月的天數(shù)。
代碼實(shí)現(xiàn)如下:
Date& Date::operator+=(int day) { //這里是處理有人傳負(fù)的天數(shù),轉(zhuǎn)化成調(diào)用-=函數(shù) if (day < 0) { return *this -= -day; } //先加上天數(shù) _day += day; //加上天數(shù)后超出了月的范圍 while (_day > GetMonthDay(_year, _month)) { //減去當(dāng)前月的天數(shù),此時(shí)月份+1 _day -= GetMonthDay(_year, _month); ++_month; //超過12個(gè)月時(shí) if (_month == 13) { ++_year;//年份+1 _month = 1;//別忘了月份還要從1月開始 } } return *this; }
11. 日期 + 天數(shù)
比如 d1 + 50,d1沒有改變。
代碼實(shí)現(xiàn)如下:
Date Date::operator+(int day) const { //實(shí)例化一個(gè)臨時(shí)的局部變量,用拷貝構(gòu)造 //把d1的日期拷貝給tmp,這樣d1就不會(huì)改變 Date tmp = *this; tmp += day;//直接復(fù)用+= //注意:出了這個(gè)函數(shù),tmp會(huì)被銷毀,所以這里不能用引用返回。 // 這里是傳值返回,所以會(huì)形成一個(gè)拷貝 return tmp; }
12. 日期-=天數(shù)
比如 d1- 50,這里的 d1也改變了。計(jì)算過程如下:
注意,這里加(借)的是下一個(gè)月的天數(shù)。
代碼實(shí)現(xiàn)如下:
Date& Date::operator-=(int day) { //這里是處理有人傳負(fù)的天數(shù),轉(zhuǎn)化成調(diào)用+=函數(shù) if (day < 0) { return *this += -day; } _day -= day; while (_day <= 0) { --_month; if (_month == 0) { _month = 12; _year--; } //借上一個(gè)月的天數(shù) _day += GetMonthDay(_year, _month); } return *this;
13. 日期 - 天數(shù)
思路同 日期 + 天數(shù)。
代碼實(shí)現(xiàn)如下:
Date Date::operator-(int day) const { Date tmp = *this; tmp -= day; return tmp; }
四,前置,后置類的重載
首先要知道前置和后置運(yùn)算的區(qū)別:
前置:返回運(yùn)算后的值;
后置:返回運(yùn)算前的值。
其次,還要理解函數(shù)重載和運(yùn)算符的重載:
函數(shù)重載:可以讓函數(shù)名相同,參數(shù)不同的函數(shù)存在;運(yùn)算符重載:讓自定義類型可以用運(yùn)算符,并且控制運(yùn)算符的行為,增強(qiáng)可讀性 。
這兩者各論各的,沒有關(guān)系。但是,多個(gè)同一運(yùn)算符重載可以構(gòu)成函數(shù)重載。
我們知道,前置和后置是同一運(yùn)算的不同形式 ,但是他們的函數(shù)名相同,參數(shù)都是隱含的this參數(shù),無(wú)法構(gòu)成重載同時(shí)存在。所以為了區(qū)分,并且構(gòu)成重載,C++規(guī)定:強(qiáng)行給后置(后置++和后置- -)函數(shù)的參數(shù)增加了一個(gè) int 形參,不需要寫形參名。并且這個(gè)形參沒有任何意義。
14. 前置++
前置++先加,后用,返回的是加之后的值,可以直接復(fù)用+=運(yùn)算符重載,返回的是改變后的 *this。
Date& Date::operator++() { *this += 1; return *this; }
15. 后置++
注意:后置函數(shù)多一個(gè)形參 int,以便與前置構(gòu)成重載。后置++是先用,后加,返回的是加之前的值。所以需要?jiǎng)?chuàng)建一個(gè)臨時(shí)的局部對(duì)象 tmp,用拷貝構(gòu)造把原來的 *this 拷貝給 tmp 。最后返回 tmp。
Date Date::operator++(int) { Date tmp = *this; *this += 1; return tmp; }
16. 前置 - -
前置- -和前置++的原理類似。
Date& Date::operator--() { *this -= 1; return *this; }
17. 后置 - -
注意:后置函數(shù)多一個(gè)形參 int,以便與前置構(gòu)成重載。原理與后置++類似。
Date Date::operator--(int) { Date tmp(*this); *this -= 1; return tmp; }
注意:
- 前置和后置運(yùn)算,一般建議用前置,因?yàn)楹笾妙愋枰截悩?gòu)造,傳值返回,這就會(huì)產(chǎn)生兩次拷貝和一次析構(gòu),而前置卻沒有這樣的消耗,相比之下前置類有優(yōu)勢(shì)。
18. 日期-日期 返回天數(shù)
兩個(gè)日期相減,返回的是相差的天數(shù),是一個(gè)整形。思路:找出大的年份和小的年份,再定義一個(gè)計(jì)數(shù)器和小的年份一起++,直到和大的年份相等。
比如 d1 - d2
代碼實(shí)現(xiàn)如下:
//隱含的this指針是d1,d是d2的別名 int Date::operator-(const Date& d) const { //先假設(shè)大日期和小日期 Date max = *this; Date min = d; //默認(rèn)假設(shè)正確 int flag = 1; //如果假設(shè)錯(cuò)誤,就進(jìn)行改正 if (*this < d) { max = d; min = *this; flag = -1; } int n = 0; //讓計(jì)數(shù)n和小的日期一起加,加到和大的日期相等 while (min != max) { ++min; ++n; } //flag的正負(fù)有妙用 return n * flag; }
五,完整代碼
Date.h
#pragma once #include <iostream> using namespace std; #include <assert.h> #include <stdbool.h> class Date { //構(gòu)造函數(shù) Date(int year, int month, int day); void Print() const; //定義為內(nèi)聯(lián)函數(shù) int GetMonthDay(int year, int month) { //斷言,確保輸入月份的有效性 assert(month > 0 && month < 13); static int monthDayArray[13] = { -1, 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; } else { return monthDayArray[month]; } } //檢查日期的合法性 bool CheakDate(); //兩個(gè)日期之間的比較 bool operator< (const Date& d) const; bool operator<= (const Date& d) const; bool operator> (const Date& d) const; bool operator>= (const Date& d) const; bool operator==(const Date& d) const; bool operator!=(const Date& d) const; //d1 += 100,d1已經(jīng)改變 Date& operator+=(int day); Date& operator-=(int day); //d1 + 50,d1不變 Date operator+(int day) const; Date operator-(int day) const; // d1 - d2 int operator-(const Date& d) const; // ++d1 -> d1.operator++() Date& operator++(); // d1++ -> d1.operator++(1) 整數(shù)任意給 Date operator++(int); //前置,后置-- Date& operator--(); Date operator--(int); private: int _year; int _month; int _day; };
Date.cpp
#define _CRT_SECURE_NO_WARNINGS #include "Date.h" bool Date::CheakDate() { if (_month < 1 || _month>12 || _day<1 || _day>GetMonthDay(_year, _month)) { return false; } else { return true; } } //1.缺省參數(shù)只能在聲明的時(shí)候給 //2.成員函數(shù)聲明與定義分離時(shí),要指定類域 Date::Date(int year, int month, int day) { _year = year; _month = month; _day = day; if (!CheakDate()) { cout << "日期非法!" << endl; } } void Date::Print() const { cout << _year << "-" << _month << "-" << _day << endl; } //思路:先比年,年小就小,年相等比月,月小就小,年月相等比日,日小就小 //d1 <d2 隱含的this是d1, d是d2的別名 bool Date::operator< (const Date& d) const { 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; } } return false; } //先寫好大于和等于 或者 小于和等于的函數(shù),其余的進(jìn)行復(fù)用 //d1 <=d2 bool Date::operator<= (const Date& d) const { return *this < d || *this == d; } bool Date::operator> (const Date& d) const { return !(*this <= d); } bool Date::operator>= (const Date& d) const { return !(*this < d); } bool Date::operator==(const Date& d) const { return _year == d._year && _month == d._month && _day == d._day; } bool Date::operator!=(const Date& d) const { return !(*this == d); } //日期 += 天數(shù) :d1 + 100 //這里的d1 已經(jīng)改了 Date& Date::operator+=(int day) { //這里是處理有人傳負(fù)的天數(shù) if (day < 0) { return *this -= -day; } //先加上天數(shù) _day += day; //加上天數(shù)后超出了月的范圍 while (_day > GetMonthDay(_year, _month)) { //減去當(dāng)前月的天數(shù),此時(shí)月份+1 _day -= GetMonthDay(_year, _month); ++_month; //超過12個(gè)月時(shí) if (_month == 13) { ++_year;//年份+1 _month = 1;//別忘了月份還要從1月開始 } } return *this; } // d1 + 50,d1沒有改變 Date Date::operator+(int day) const { //實(shí)例化一個(gè)臨時(shí)的局部變量,用拷貝構(gòu)造,把d1的日期拷貝給tmp,這樣d1就不會(huì)改變 Date tmp = *this; tmp += day;//直接復(fù)用+= //注意:出了這個(gè)函數(shù),tmp會(huì)被銷毀,所以這里不能用引用返回。 // 這里是傳值返回,所以會(huì)形成一個(gè)拷貝 return tmp; } //日期 -= 天數(shù) :d1 - 100 //這里的d1 已經(jīng)改了 Date& Date::operator-=(int day) { if (day < 0) { return *this += -day; } _day -= day; while (_day <= 0) { --_month; if (_month == 0) { _month = 12; _year--; } //借上一個(gè)月的天數(shù) _day += GetMonthDay(_year, _month); } return *this; } Date Date::operator-(int day) const { Date tmp = *this; tmp -= day; return tmp; } // ++d Date& Date::operator++() { *this += 1; return *this; } //這兩種++ 建議用前置++,因?yàn)楹笾?+會(huì)產(chǎn)生兩次拷貝和一次析構(gòu),相比之下前置++有優(yōu)勢(shì)。 //d++ Date Date::operator++(int) { Date tmp = *this; *this += 1; return tmp; } //--d Date& Date::operator--() { *this -= 1; return *this; } //d-- Date Date::operator--(int) { Date tmp(*this); *this -= 1; return tmp; } //思路:找出大的年份和小的年份,再定義一個(gè)計(jì)數(shù)器和小的年份一起++,直到和大的年份相等。 //d1 - d2 int Date::operator-(const Date& d) const { Date max = *this; Date min = d; int flag = 1; if (*this < d) { max = d; min = *this; flag = -1; } int n = 0; while (min != max) { ++min; ++n; } return n * flag; }
Test.cpp
#define _CRT_SECURE_NO_WARNINGS #include "Date.h" void TestDate1() { Date d1(2024, 4, 14); Date d2 = d1 + 5000; d1.Print(); d2.Print(); Date d3(2024, 4, 14); Date d4 = d3 - 5000; d3.Print(); d4.Print(); Date d5 (2024, 4, 14); d5 += -5000;//轉(zhuǎn)化成-=運(yùn)算,計(jì)算5000天之前的時(shí)間 d5.Print(); } void TestDate2() { Date d1(2024, 4, 14); Date d2 = ++d1; d1.Print(); d2.Print(); Date d3 = d1++; d1.Print(); d3.Print(); } int main() { TestDate1(); return 0; }
日期的比較類比較簡(jiǎn)單,不在這里示范,讀者自行驗(yàn)證。
比如,調(diào)用TestDate1()計(jì)算未來的日期和以前的日期:
再比如,調(diào)用TestDate2()觀察前置與后置運(yùn)算的區(qū)別:
總結(jié)
到此這篇關(guān)于C++日期類的實(shí)現(xiàn)日期計(jì)算器的文章就介紹到這了,更多相關(guān)C++日期類實(shí)現(xiàn)日期計(jì)算器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++聚合關(guān)系類的構(gòu)造函數(shù)的調(diào)用順序詳解
下面小編就為大家?guī)硪黄狢++聚合關(guān)系類的構(gòu)造函數(shù)的調(diào)用順序詳解。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考,一起跟隨小編過來看看吧2016-05-05Opencv實(shí)現(xiàn)讀取攝像頭和視頻數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了Opencv實(shí)現(xiàn)讀取攝像頭和視頻數(shù)據(jù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01VC外部符號(hào)錯(cuò)誤_main,_WinMain@16,__beginthreadex解決方法
這篇文章主要介紹了VC外部符號(hào)錯(cuò)誤_main,_WinMain@16,__beginthreadex解決方法,實(shí)例分析了比較典型的錯(cuò)誤及對(duì)應(yīng)的解決方法,需要的朋友可以參考下2015-05-05C++ 封裝 DLL 供 C# 調(diào)用詳細(xì)介紹
這篇文章主要介紹了C++ 封裝 DLL 供 C# 調(diào)用(以C# 調(diào)用C++ 二次封裝的VLC播放庫(kù)為介質(zhì),支持回調(diào)函數(shù)的封裝),需要的朋友可以參考下面我文章的具體內(nèi)容2021-09-09C++實(shí)現(xiàn)LeetCode(241.添加括號(hào)的不同方式)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(241.添加括號(hào)的不同方式),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07Visual Studio Code (VSCode) 配置搭建 C/C++ 開發(fā)編譯環(huán)境的流程
記得N年前剛開始接觸編程時(shí),使用的是Visual C++6.0,下面這個(gè)可愛的圖標(biāo)很多人一定很熟悉。不過今天想嘗鮮新的工具 Visual Studio Code 來搭建C/C++開發(fā)環(huán)境,感興趣的朋友一起看看吧2021-09-09