Date类成员变量
通常来说,一个日期是由三部分构成的,分别是:年,月,日。由此,日期类的成员变量就很好制定,即三个整形,分别为:_year,_month,_day,如下:
class Date { public: //类函数声明/定义部分 //成员变量一般需要保护起来 private: int _year; int _month; int _day; };
Date类默认成员函数
Date类构造函数
C++中一个类包含6个默认成员函数,如下:
对于日期类而言,它的三个成员变量均不涉及动态资源的申请,因此,我们可以不用为其写专门的析构函数,而且它的三个成员变量也都不涉及指针,因此拷贝构造函数和赋值运算符重载函数也不需要我们自己写,因为编译器默认生成的浅拷贝对它而言就是够用的了。最后两个取地址重载函数对于绝大多数类而言一般都不需要我们自己实现。
对于类的六个默认成员函数还不了解的朋友可以移步: 【C++】类的六大默认成员函数及其特性(万字详解)
综上所述,对于日期类中的6个默认成员函数,我们只需要自己实现一下构造函数即可:
class Date { public: //Date类构造函数声明(一般函数缺省值是在声明给,定义时不能再提供缺省值) Date(int year = 1, int month = 1, int day = 1); private: int _year; int _month; int _day; }; Date::Date(int year, int month, int day) { //防止构造非法日期 if (month > 0 && month < 13 && day>0 && day <= GetMonthDay(year, month)) { _year = year; _month = month; _day = day; } else { cout << "非法日期" << endl; assert(false); } }
(注,上述构造函数中调用了一个函数GetMonthDay,该函数作用是返回该月的天数,在后面部分也会带领大家实现的,所以现在先清楚它的功能就可以)
因为我们是在类里声明,类外定义成员函数,因此在定义的时候要在前面加上类名及限定符。然后对于无参调用构造函数的变量呢,我们期望它自动初始化日期为1-1-1,对于有参数调用构造函数的变量,成员变量的值初始化为其传入的相应的参数.
如下,我们在主函数创建两个类d1,d2和非法值d3,分别测试一下构造函数的功能:
void test3() { Date d1; Date d2(2024, 3, 17); d1.Print(); d2.Print(); Date d3(2024, 13, 1); d3.Print(); } int main() { test3(); return 0; }
运行程序,对于合法的无参构造d1,构造函数成功创建其日期为1-1-1;对于合法的有参构造d2,构造函数成功创建其日期为参数值2024-3-17;对于不合法的有参构造d3,构造函数给予提醒并报错:
Date类成员函数
Date类日期比较大小函数
注意,该部分的成员函数都是使用C++的运算符重载特性构造的,因此函数命名会是operator运算符的形式,如果有对运算符重载函数还不太了解的朋友可以先移步:【C++】类的六大默认成员函数及其特性(万字详解)
其中有关于运算符重载的入门级详解:
判断日期类变量大于
判断日期类变量的大小,其算法逻辑可以分为四个步骤,即:
- 先判断年是否大于,如果是,则大于
- 年相同的情况下判断月份是否大于,如果是,则大于
- 年相同且月相同的情况下判断天是否大于,如果是,则大于
- 如果1,2,3都不是,则不大于.
因为是第一次构造类成员函数,所以借这个函数详解一下其中一些区别于C语言的特性:
对于const修饰,需要补充的是,我们给不改变传参内容的函数参数加上const修饰不仅仅是为了防止在函数里不慎将这些值修改了,还为了如果有const修饰的变量调用这些函数时也可以正常调用,否则const修饰变量传入无const修饰形参是一种权限放大的行为,编译器是不会通过的,而无const修饰变量传入const修饰形参是一种权限放小行为,这个是被允许的,所以编译器是可以通过的。
综上,根据算法逻辑以及C++语言特性,判断日期类变量大于函数如下:
bool Date::operator>(const Date& x) const { if (_year > x._year) { return true; } else if (_year == x._year && _month > x._month) { return true; } else if (_year == x._year && _month == x._month && _day > x._day) { return true; } return false; }
我们构造三个变量测试一下大于函数:
判断日期类变量等于
判断日期类变量等于的算法逻辑就比简单了,即年相等并且月相等并且日相等,代码如下:
bool Date::operator==(const Date& x)const { return _year == x._year && _month == x._month && _day == x._day; }
我们构造三个变量测试一下等于函数:
判断日期类变量不等于
判断日期变量不等于的逻辑也很简单,即年不相等或月不相等或日不相等,但我们已经有等于函数了,复用一下就可以判断不相等,就不用再写完整的判不等的逻辑函数了,代码如下:
bool Date::operator!=(const Date& x)const { return !(operator==(x)); }
我们同样构造三个变量测试一下不等函数:
判断日期类变量大于等于
因为我们已经有大于函数和等于了,则大于等于的逻辑复用即可,即大于或等于,代码如下:
bool Date::operator>=(const Date& x)const { return ( * this > x || *this == x ); }
我们同样构造三个变量测试一下大于等于函数:
判断日期类变量小于
因为我们已经有大于等于函数了,而不大于等于就是小于,因此我们复用一下大于等于函数完成小于函数,代码如下:
bool Date::operator<(const Date& x)const { return !(*this>=x); }
我们同样构造三个变量测试一下小于函数:
判断日期类变量小于等于
因为我们已经有大于函数了,而不大于就是小于等于,因此我们复用一下大于函数完成小于等于函数,代码如下:
bool Date::operator<=(const Date& x)const { return !(*this>x); }
我们同样构造三个变量测试一下小于等于函数:
Date类日期加减天数函数
获取月份天数函数
要获取月份天数,首先要判断是不是二月,如果是二月则还需要判断是不是闰年,是闰年则返回29天,不是则返回28天,如果不是二月,则可以根据月份直接返回相应数组数值,代码如下:
int Date::GetMonthDay(int year, int month) { static int monthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; //因为这个函数一定会被频繁调用,每次都开数组存数据有些浪费, //所以直接开一次然后利用static关键字扔静态区,就可以优化不少性能 if ((month == 2) && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))) //把2月放前面判,大概率不用判断闰年就走else了 { return 29; } else { return monthDays[month]; } }
我们测试一下该函数:
日期加等天数
日期加等天数的算法逻辑是:
- 判断天数是否是负数,如果是负数,则加等负的天数等于减等正的天数
- 把天数全部加起来
- 如果天数超过了本月的天数,则减去本月天数后给月份加一
- 如果月份超过了12,则给月份减去12后给年份加一
- 重复步骤3和4,直到天数不超过当月天数为止
又因为是加等,所以我们可以直接修改this指针的成员变量.综上,函数代码如下:
//有返回值防止连续加等 Date& Date::operator+=(int day) { if (day < 0) { return *this -= -day; } //先把天加起来,然后天满进月,月满进年,直到天不满为止 _day += day; while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); _month++; if (_month > 12) { _year++; _month -= 12; } } return *this; }
我们测试一下加等函数:
日期加天数
日期加天数和日期加等天数的算术逻辑是一样的,区别仅仅在于,日期+天数后自身不改变,因此我们在函数里创建一个临时变量替原本的日期加等后,返回这个临时变量即可:
//因为+不能改变*this,因此可以在函数内拷贝构造一个临时的*this Date Date::operator+(int day)const { Date tmp(*this); tmp += day; return tmp; }
测试一下加函数:
日期类变量前置++
日期类的前置++,其实就是+=1,然后返回+=1后的结果,代码如下:
//前置++,返回++后的值 //对自定义类型尽量用前置++,因为不用创建形参,并且可以使用引用返回 Date& Date::operator++() { *this += 1; return *this; }
我们测试一下前置++:
日期类变量后置++
日期类的后置++,就是给日期+=1,然后返回+=1前的结果,因此我们就需要在后置++函数里拷贝构造一个形参来记录this没有++前的值。
而因为临时变量出了作用域就会销毁,因此我们不能使用引用返回,而这又导致返回临时变量时需要再拷贝构造一个临时变量,因此自定义类型后置++就会比前置++多两个拷贝构造的消耗。
所以对于自定义类型,在不影响程序正常功能的情况下,我们更推荐使用前置++.
需要注意的是,因为为了和前置++构成函数重载,因此后置++函数加了一个形参int,但这个形参仅是为了构成函数重载,而并非为了使用,不使用就可以不写形参名.
对于前置++,编译器在识别到时会将其转换为:
//如: ++d1; //会自动转换为调用: d1.operator++();
而对于后置++,由于多了一个形参的缘故,编译器在识别到时会将其转换为:
//如: d1++; //会自动转换为调用: d1.operator++(0); //这个形参位置的0可以是任意整形,主要看编译器如何设定
综上,后置++函数代码如下:
//后置++,返回++前的值 //为了和前置++构成函数重载,因此加了一个形参int,但这个形参仅是为了构成函数重载, //而并非为了使用,不使用就可以不写形参名 Date Date::operator++(int) { Date tmp(*this); *this += 1; return tmp; }
我们测试一下后置++:
日期减等天数
日期减等天数的逻辑和日期加等天数类似,只是将加等的多的天数向本月进一变成了将减等的缺的天数向上月借一,直到天数不再少于等于0为止,代码如下:
//有返回值防止连续减等 Date& Date::operator-=(int day) { //先把天减起来,然后天缺减月,月缺减年,直到天不缺为止 if (day < 0) { return *this += -day; } _day -= day; while (_day <= 0) { --_month; if (_month <= 0) { _year--; _month += 12; } _day += GetMonthDay(_year, _month); } return *this; }
我们测试一下减等函数:
日期减天数
和加天数逻辑类似,代码如下:
//因为-不能改变*this,因此可以在函数内拷贝构造一个临时的*this Date Date::operator-(int day)const { Date tmp(*this); tmp -= day; return tmp; }
测试一下减天数:
日期类变量前置--
和前置++逻辑类似,代码如下:
//前置-- Date& Date::operator--() { *this -= 1; return *this; }
测试前置--:
日期类变量后置--
和后置++逻辑类似,代码如下:
//后置-- Date Date::operator--(int) { Date tmp(*this); *this -= 1; return tmp; }
测试一下后置--:
日期减日期
日期减日期得到其中间隔的天数,该函数有很多种实现思路,比如分别让年相减,月相减,日相减,再算中间的天数,但这样实现上还是有些复杂,我们采用的方法是,拷贝其中一个日期,然后让这个拷贝的日期不断加/减一天,逐渐向另一个日期接近,并设置一个计数器来记录一共加/减了多少个一天,直到两个日期相等,返回计数器的值,就是日期减日期的值:
注意,因为我们是使用临时变量来靠近另一个日期的,所以传入的两个日期我们都不会改变,所以都要加上const进行修饰.
综上,代码如下:
//日期减日期 int Date::operator-(const Date& x)const { int count = 0; //为了不影响-的两个操作数,所以创建一个变量tmp来向另一个操作数拷靠近 Date tmp(x); if (*this < tmp) { while (*this != tmp) { count--; tmp--; } return count; } else { while (*this != tmp) { count++; tmp++; } return count; } }
我们测试一下日期相减函数:
Date类日期流插入和流提取函数
日期类流插入函数
流插入不能写成成员函数,因为成为成员函数后Date对象默认占用第一个参数,就成为了左操作数
ostream& Date::operator<<(ostream& out) { out << _year << "年" << _month << "月" << _day << "日" << endl; return out; }
那么调用时一定是这个样子:
d1 << cout;
这并不符合我们的调用习惯,我们总是习惯这样调用流插入:
cout << d1;
如果要符合我们的调用习惯,只能改库了,把库里的cout重载一下,让它也能支持Date类的打印,只有这样ostream类的this指针才能占据第一个隐含参数的位置。
但是如果写成有两个参数的全局函数,又会面临private修饰的成员变量无法访问的问题,对于此问题,解决方案有两个:
- 在日期类中加几个提供成员变量的函数,如GetYear(),GetMonth(),GetDat()等...,然后全局函数通过调用这些公开的函数获得成员变量的值。
- 让这个函数变成Date类的友元。
我们采用方法二,则先在类里声明该友元函数,然后就可以直接正常使用了,声明如下:
class Date { //友元函数流插入和流提取的函数声明 friend ostream& operator<<(ostream& out,const Date& d); friend istream& operator>>(istream& out, Date& d); public: //类函数声明/定义部分 private: //成员变量一般需要保护起来 int _year; int _month; int _day; };
综上,流插入函数代码如下:
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; if (d._month > 0 && d._month < 13 && d._day>0 && d._day <= Date::GetMonthDay(d._year, d._month)) { return in; } else { cout << "非法日期" << endl; assert(false); } }
我们测试一下流提取函数,先输入合法数据d1:
再输入非法数据d2:
完整Date类代码
Date.h文件
文件主要包含Date类所需头文件及Date类的相关定义及函数声明
#pragma once #include<iostream> #include<assert.h> using namespace std; class Date { friend ostream& operator<<(ostream& out,const Date& d); friend istream& operator>>(istream& out, Date& d); public: Date(int year = 1, int month = 1, int day = 1); //对于不改变类成员变量的成员函数,最好都加上const修饰 // 否则const成员就不能调函数了 // 这个const,修饰的是默认的*this指针 void Print()const { cout << _year << "-" << _month << "-" << _day << endl; } bool operator>(const Date& x) const; bool operator==(const Date& x) const; bool operator>=(const Date& x) const; bool operator<=(const Date& x) const; bool operator<(const Date& x) const; bool operator!=(const Date& x) const; static int GetMonthDay(int year, int month); Date& operator+=(int day); Date operator+(int day)const; Date& operator++(); Date operator++(int); Date& operator-=(int day); Date operator-(int day) const; Date& operator--(); Date operator--(int); int operator-(const Date& x) const; private: int _year; int _month; int _day; };
Date.cpp文件
文件主要包含Date类成员函数及相关函数的定义
#include"Date.h" Date::Date(int year, int month, int day) { if (month > 0 && month < 13 && day>0 && day <= GetMonthDay(year, month)) { _year = year; _month = month; _day = day; } else { cout << "非法日期" << endl; assert(false); } } bool Date::operator>(const Date& x) const { if (_year > x._year) { return true; } else if (_year == x._year && _month > x._month) { return true; } else if (_year == x._year && _month == x._month && _day > x._day) { return true; } return false; } bool Date::operator==(const Date& x)const { return _year == x._year && _month == x._month && _day == x._day; } bool Date::operator>=(const Date& x)const { return ( * this > x || *this == x ); } bool Date::operator<=(const Date& x)const { return !(*this>x); } bool Date::operator<(const Date& x)const { return !(*this>=x); } bool Date::operator!=(const Date& x)const { return !(*this==x); } int Date::GetMonthDay(int year, int month) { static int monthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; //因为这个函数一定会被频繁调用,每次都开数组存数据有些浪费,所以直接开一次然后仍静态区,就可以优化不少性能 if ((month == 2) && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)))//把2月放前面判,大概率不用判断闰年就走else了 { return 29; } else { return monthDays[month]; } } //有返回值防止连续加等 Date& Date::operator+=(int day) { if (day < 0) { return *this -= -day; } //先把天加起来,然后天满进月,月满进年,直到天不满为止 _day += day; while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); _month++; if (_month > 12) { _year++; _month -= 12; } } return *this; } //因为+不能改变*this,因此可以在函数内拷贝构造一个临时的*this Date Date::operator+(int day)const { Date tmp(*this); tmp += day; return tmp; } //前置++,返回++后的值 //对自定义类型尽量用前置++,因为不用创建形参,并且可以使用引用返回 Date& Date::operator++() { *this += 1; return *this; } //后置++,返回++前的值 //为了和前置++构成函数重载,因此加了一个形参int,但这个形参仅是为了构成函数重载,而并非为了使用,不使用就可以不写形参名 Date Date::operator++(int) { Date tmp(*this); *this += 1; return tmp; } //有返回值防止连续减等 Date& Date::operator-=(int day) { //先把天减起来,然后天缺减月,月缺减年,直到天不缺为止 if (day < 0) { return *this += -day; } _day -= day; while (_day <= 0) { --_month; if (_month <= 0) { _year--; _month += 12; } _day += GetMonthDay(_year, _month); } return *this; } //因为-不能改变*this,因此可以在函数内拷贝构造一个临时的*this Date Date::operator-(int day)const { Date tmp(*this); tmp -= day; return tmp; } //前置-- Date& Date::operator--() { *this -= 1; return *this; } //后置-- Date Date::operator--(int) { Date tmp(*this); *this -= 1; return tmp; } //日期减日期 int Date::operator-(const Date& x)const { int count = 0; //为了不影响-的两个操作数,所以创建一个变量tmp来向另一个操作数拷靠近 Date tmp(x); if (*this < tmp) { while (*this != tmp) { count--; tmp--; } return count; } else { while (*this != tmp) { count++; tmp++; } return count; } } //流插入不能写成成员函数 //因为成为成员函数后Date对象默认占用第一个参数,就成为了左操作数 //那么调用时一定是这个样子: //d1 << cout; //如果要符合我们的调用习惯呢,那只能改库了,把库里的cout重载一下,让它也能支持Date类的打印 //void Date::operator<<(ostream& out) //{ // out << _year << "年" << _month << "月" << _day << "日" << endl; //} //解决方案有2 //1.在日期类中加几个提供成员变量的函数,如GetYear(),GetMonth(),GetDat()等...,然后这个函数调用这些函数获得成员变量 //2.让这个函数变成Date类的友元 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; if (d._month > 0 && d._month < 13 && d._day>0 && d._day <= Date::GetMonthDay(d._year, d._month)) { return in; } else { cout << "非法日期" << endl; assert(false); } }
Test.cpp文件
文件主要包含Date类函数功能测试函数
void test4() { Date d1(2024, 3, 17); Date d2(2024, 7, 1); Date d3(2023, 12, 1); cout << "d1>d2? " << (d1 > d2) << endl; cout << "d1>d3? " << (d1 > d3) << endl; } void test5() { Date d1(2024, 3, 17); Date d2(2024, 7, 1); Date d3(2024, 3, 17); cout << "d1==d2? " << (d1 == d2) << endl; cout << "d1==d3? " << (d1 == d3) << endl; } void test6() { Date d1(2024, 3, 17); Date d2(2024, 7, 1); Date d3(2024, 3, 17); cout << "d1!=d2? " << (d1 != d2) << endl; cout << "d1!=d3? " << (d1 != d3) << endl; } void test7() { Date d1(2024, 3, 17); Date d2(2024, 7, 1); Date d3(2023, 12, 1); cout << "d1>=d2? " << (d1 >= d2) << endl; cout << "d1>=d3? " << (d1 >= d3) << endl; } void test8() { Date d1(2024, 3, 17); Date d2(2024, 7, 1); Date d3(2023, 12, 1); cout << "d1<d2? " << (d1 < d2) << endl; cout << "d1<d3? " << (d1 < d3) << endl; } void test9() { Date d1(2024, 3, 17); Date d2(2024, 7, 1); Date d3(2024, 3, 17); cout << "d1<=d2? " << (d1 <= d2) << endl; cout << "d1<=d3? " << (d1 <= d3) << endl; } void test10() { Date d1; cout << "2024,1月有多少天? " << d1.GetMonthDay(2024, 1) << endl; cout << "2024,2月有多少天? " << d1.GetMonthDay(2024, 2) << endl; cout << "2024,3月有多少天? " << d1.GetMonthDay(2024, 3) << endl; cout << "2024,4月有多少天? " << d1.GetMonthDay(2024, 4) << endl; cout << "2024,5月有多少天? " << d1.GetMonthDay(2024, 5) << endl; cout << "2024,6月有多少天? " << d1.GetMonthDay(2024, 6) << endl; cout << "2024,7月有多少天? " << d1.GetMonthDay(2024, 7) << endl; cout << "2024,8月有多少天? " << d1.GetMonthDay(2024, 8) << endl; cout << "2024,9月有多少天? " << d1.GetMonthDay(2024, 9) << endl; cout << "2024,10月有多少天? " << d1.GetMonthDay(2024, 10) << endl; cout << "2024,11月有多少天? " << d1.GetMonthDay(2024, 11) << endl; cout << "2024,12月有多少天? " << d1.GetMonthDay(2024, 12) << endl; } void test11() { Date d1(2021, 4, 8); d1 += 999; cout << "d1+=999? "<< endl; d1.Print(); } void test12() { Date d1(2021, 4, 8); Date d2 = d1 + 999; cout << "d2 = d1 + 999 ? " << endl; d1.Print(); d2.Print(); } void test13() { Date d1(2021, 4, 8); Date d2 = ++d1; d1.Print(); d2.Print(); } void test14() { Date d1(2021, 4, 8); Date d2 = d1++; d1.Print(); d2.Print(); } void test15() { Date d1(2024, 3, 14); d1 -= 1000; cout << "d1-=1000? " << endl; d1.Print(); } void test16() { Date d1(2024, 3, 14); Date d2 = d1 - 1000; cout << "d2 = d1 - 1000 ? " << endl; d1.Print(); d2.Print(); } void test17() { Date d1(2021, 4, 8); Date d2 = --d1; d1.Print(); d2.Print(); } void test18() { Date d1(2021, 4, 8); Date d2 = d1--; d1.Print(); d2.Print(); } void test19() { Date d1(2021, 4, 8); Date d2(2024, 1, 2); cout << d2 - d1 << endl; } void test20() { Date d1(2021, 4, 8); Date d2(2024, 1, 2); cout << d1 << d2 << endl; } void test21() { Date d1; cin >> d1; cout << d1; Date d2; cin >> d2; cout << d2; } int main() { //在这里调用相应的测试函数即可 return 0; }
结语
希望这篇关于Date类的实现博客能对大家有所帮助,欢迎大佬们留言或私信与我交流.
学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!