C++日期类的实现日期计算器举例详解
作者:唉苏菲夫妇
前言
1.日期类是一种十分经典的类型。对于C++的初学者,它能够帮助我们融会贯通许多C++的基础知识,它涉及许多的基础语法,比如引用,函数重载,传值/传参返回,构造函数,运算符重载,const成员等等。
如果有不了解的,可以前往我的主页浏览相关文章。
日期计算器可以实现两个日期的比较,两个日期的相减,日期的加减天数等有意义的运算。
2.本文依然采用多文件方式。其中:
Date.h //定义类,存放各函数的声明;Date.cpp //实现各重载函数;Test.cpp //测试各函数的功能。
在C++中,由于函数的声明与定义分离,如果要定义成员函数,就要指定类域,这是基本语法。
一,各个函数功能的实现
1. 检查输入的日期是否合法
不管是日期的比较还是日期的运算,第一步都要检查日期的合法性。特别是月份和每个月的天数。
代码实现如下:
bool Date::CheakDate() { if (_month < 1 || _month>12 || _day<1 || _day>GetMonthDay(_year, _month)) { return false; } else { return true; } }
2. 构造函数 (初始化函数)
为了方便,在使用默认构造函数时,一般是自己显式的实现一个全缺省构造函数。
注意:在函数的声明和定义分离时,如果要给缺省值,必须在函数声明的时候给。
代码实现如下:
Date::Date(int year, int month, int day) { _year = year; _month = month; _day = day; //日期的源头,是从构造函数里出来的,所以要在这里判断 if (!CheakDate()) { cout << "日期非法!" << endl; } }
二,比较类的运算符重载
3. <运算符重载
判断两个日期谁更小。思路:先比年,年小就小,年相等比月,月小就小,年月相等比日,日小就小。
代码实现如下:
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. ==运算符重载
判断两个日期是否相等 。这个比较简单,如果两者的年月日都相等,即相等。
代码实现如下:
bool Date::operator==(const Date& d) const { return _year == d._year && _month == d._month && _day == d._day; }
5. >=运算符重载
有人可能会仿照<运算符重载的方法,使用复杂的逻辑,写各种晦涩的代码实现。其实只要实现了<运算符重载和==运算符重载,下面的日期比较类都是可以复用的。 比如这里的>=,< 取反就是>=。
代码实现如下:
bool Date::operator>= (const Date& d) const { return !(*this < d); }
6. >运算符重载
<= 取反,就是>。
bool Date::operator> (const Date& d) const { return !(*this <= d); }
7. <=运算符重载
只要满足<或者=,就是<=。
bool Date::operator<= (const Date& d) const { return *this < d || *this == d; }
8. !=运算符重载
==去取反,就是!=。
bool Date::operator!=(const Date& d) const { return !(*this == d); }
9. 获取某月的天数
这个函数是整个日期类的关键,也是最频繁调用的一个函数。由于这个原因,最好把它定义成内联函数,避免每次调用都要开辟空间,可以提升效率。根据C++的语法,定义在类里默认是内联,inline可加可不加。
代码实现如下:
这里还有两个优化的细节:
1. month == 2 和后面的取模运算的位置。首先满足是2月,再判断是否是闰年,效率会更高。2. static的使用,由于该函数频繁调用,把数组放在静态区,避免每次调用函数时每次都要开辟数组空间。
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 }; //判断2月的平年和闰年 if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) { return 29; } else { return monthDayArray[month]; } }
三,运算类的重载
10. 日期+=天数
比如d1 + 50,这里的d1已经改变了。
计算过程如下:
注意,每次超过月份天数后,是减当前月的天数。
代码实现如下:
Date& Date::operator+=(int day) { //这里是处理有人传负的天数,转化成调用-=函数 if (day < 0) { return *this -= -day; } //先加上天数 _day += day; //加上天数后超出了月的范围 while (_day > GetMonthDay(_year, _month)) { //减去当前月的天数,此时月份+1 _day -= GetMonthDay(_year, _month); ++_month; //超过12个月时 if (_month == 13) { ++_year;//年份+1 _month = 1;//别忘了月份还要从1月开始 } } return *this; }
11. 日期 + 天数
比如 d1 + 50,d1没有改变。
代码实现如下:
Date Date::operator+(int day) const { //实例化一个临时的局部变量,用拷贝构造 //把d1的日期拷贝给tmp,这样d1就不会改变 Date tmp = *this; tmp += day;//直接复用+= //注意:出了这个函数,tmp会被销毁,所以这里不能用引用返回。 // 这里是传值返回,所以会形成一个拷贝 return tmp; }
12. 日期-=天数
比如 d1- 50,这里的 d1也改变了。计算过程如下:
注意,这里加(借)的是下一个月的天数。
代码实现如下:
Date& Date::operator-=(int day) { //这里是处理有人传负的天数,转化成调用+=函数 if (day < 0) { return *this += -day; } _day -= day; while (_day <= 0) { --_month; if (_month == 0) { _month = 12; _year--; } //借上一个月的天数 _day += GetMonthDay(_year, _month); } return *this;
13. 日期 - 天数
思路同 日期 + 天数。
代码实现如下:
Date Date::operator-(int day) const { Date tmp = *this; tmp -= day; return tmp; }
四,前置,后置类的重载
首先要知道前置和后置运算的区别:
前置:返回运算后的值;
后置:返回运算前的值。
其次,还要理解函数重载和运算符的重载:
函数重载:可以让函数名相同,参数不同的函数存在;运算符重载:让自定义类型可以用运算符,并且控制运算符的行为,增强可读性 。
这两者各论各的,没有关系。但是,多个同一运算符重载可以构成函数重载。
我们知道,前置和后置是同一运算的不同形式 ,但是他们的函数名相同,参数都是隐含的this参数,无法构成重载同时存在。所以为了区分,并且构成重载,C++规定:强行给后置(后置++和后置- -)函数的参数增加了一个 int 形参,不需要写形参名。并且这个形参没有任何意义。
14. 前置++
前置++先加,后用,返回的是加之后的值,可以直接复用+=运算符重载,返回的是改变后的 *this。
Date& Date::operator++() { *this += 1; return *this; }
15. 后置++
注意:后置函数多一个形参 int,以便与前置构成重载。后置++是先用,后加,返回的是加之前的值。所以需要创建一个临时的局部对象 tmp,用拷贝构造把原来的 *this 拷贝给 tmp 。最后返回 tmp。
Date Date::operator++(int) { Date tmp = *this; *this += 1; return tmp; }
16. 前置 - -
前置- -和前置++的原理类似。
Date& Date::operator--() { *this -= 1; return *this; }
17. 后置 - -
注意:后置函数多一个形参 int,以便与前置构成重载。原理与后置++类似。
Date Date::operator--(int) { Date tmp(*this); *this -= 1; return tmp; }
注意:
- 前置和后置运算,一般建议用前置,因为后置类需要拷贝构造,传值返回,这就会产生两次拷贝和一次析构,而前置却没有这样的消耗,相比之下前置类有优势。
18. 日期-日期 返回天数
两个日期相减,返回的是相差的天数,是一个整形。思路:找出大的年份和小的年份,再定义一个计数器和小的年份一起++,直到和大的年份相等。
比如 d1 - d2
代码实现如下:
//隐含的this指针是d1,d是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; //让计数n和小的日期一起加,加到和大的日期相等 while (min != max) { ++min; ++n; } //flag的正负有妙用 return n * flag; }
五,完整代码
Date.h
#pragma once #include <iostream> using namespace std; #include <assert.h> #include <stdbool.h> class Date { //构造函数 Date(int year, int month, int day); void Print() const; //定义为内联函数 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(); //两个日期之间的比较 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已经改变 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) 整数任意给 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.缺省参数只能在声明的时候给 //2.成员函数声明与定义分离时,要指定类域 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; } //先写好大于和等于 或者 小于和等于的函数,其余的进行复用 //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); } //日期 += 天数 :d1 + 100 //这里的d1 已经改了 Date& Date::operator+=(int day) { //这里是处理有人传负的天数 if (day < 0) { return *this -= -day; } //先加上天数 _day += day; //加上天数后超出了月的范围 while (_day > GetMonthDay(_year, _month)) { //减去当前月的天数,此时月份+1 _day -= GetMonthDay(_year, _month); ++_month; //超过12个月时 if (_month == 13) { ++_year;//年份+1 _month = 1;//别忘了月份还要从1月开始 } } return *this; } // d1 + 50,d1没有改变 Date Date::operator+(int day) const { //实例化一个临时的局部变量,用拷贝构造,把d1的日期拷贝给tmp,这样d1就不会改变 Date tmp = *this; tmp += day;//直接复用+= //注意:出了这个函数,tmp会被销毁,所以这里不能用引用返回。 // 这里是传值返回,所以会形成一个拷贝 return tmp; } //日期 -= 天数 :d1 - 100 //这里的d1 已经改了 Date& Date::operator-=(int day) { if (day < 0) { return *this += -day; } _day -= day; while (_day <= 0) { --_month; if (_month == 0) { _month = 12; _year--; } //借上一个月的天数 _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; } //这两种++ 建议用前置++,因为后置++会产生两次拷贝和一次析构,相比之下前置++有优势。 //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; } //思路:找出大的年份和小的年份,再定义一个计数器和小的年份一起++,直到和大的年份相等。 //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;//转化成-=运算,计算5000天之前的时间 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; }
日期的比较类比较简单,不在这里示范,读者自行验证。
比如,调用TestDate1()计算未来的日期和以前的日期:
再比如,调用TestDate2()观察前置与后置运算的区别:
总结
到此这篇关于C++日期类的实现日期计算器的文章就介绍到这了,更多相关C++日期类实现日期计算器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!