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ù)里出來(lái)的,所以要在這里判斷
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ì)算過(guò)程如下:
注意,每次超過(guò)月份天數(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;
//超過(guò)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ì)算過(guò)程如下:
注意,這里加(借)的是下一個(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)造把原來(lái)的 *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;
//超過(guò)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ì)算未來(lái)的日期和以前的日期:

再比如,調(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ī)?lái)一篇C++聚合關(guān)系類的構(gòu)造函數(shù)的調(diào)用順序詳解。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧2016-05-05
Opencv實(shí)現(xiàn)讀取攝像頭和視頻數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了Opencv實(shí)現(xiàn)讀取攝像頭和視頻數(shù)據(jù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
VC外部符號(hào)錯(cuò)誤_main,_WinMain@16,__beginthreadex解決方法
這篇文章主要介紹了VC外部符號(hào)錯(cuò)誤_main,_WinMain@16,__beginthreadex解決方法,實(shí)例分析了比較典型的錯(cuò)誤及對(duì)應(yīng)的解決方法,需要的朋友可以參考下2015-05-05
C++ 封裝 DLL 供 C# 調(diào)用詳細(xì)介紹
這篇文章主要介紹了C++ 封裝 DLL 供 C# 調(diào)用(以C# 調(diào)用C++ 二次封裝的VLC播放庫(kù)為介質(zhì),支持回調(diào)函數(shù)的封裝),需要的朋友可以參考下面我文章的具體內(nèi)容2021-09-09
C++實(shí)現(xiàn)LeetCode(241.添加括號(hào)的不同方式)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(241.添加括號(hào)的不同方式),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
Visual Studio Code (VSCode) 配置搭建 C/C++ 開發(fā)編譯環(huán)境的流程
記得N年前剛開始接觸編程時(shí),使用的是Visual C++6.0,下面這個(gè)可愛的圖標(biāo)很多人一定很熟悉。不過(guò)今天想嘗鮮新的工具 Visual Studio Code 來(lái)搭建C/C++開發(fā)環(huán)境,感興趣的朋友一起看看吧2021-09-09

