在类与对象的学习过程中,我们经常会用日期类来测试,下面我们就用规范的方式,一起来实现一个完整的日期类,以此来加深对类与对象的理解,方便后期熟练使用。
本篇所涉及的所有代码均整理到了以下链接,如有需要,欢迎参考指正!
日期类的实现 · 王哲/practice - 码云 - 开源中国 (gitee.com)
创建项目及文件:
为规范化项目,我们共需创建:头文件Date.h(实现日期类声明);源文件Date.cpp(实现日期类定义);源文件Test.cpp(用于测试类功能是否正确)。

日期类具体实现过程:
头文件Date.h:
//Date.h
#pragma once
#include<iostream>
using namespace std;
class Date
{
public:
//日期类成员变量均为内置类型,编译器生成默认的构造函数不对内置类型处理,故需我们自己实现
Date(int year=2023, int month=1, int day=1);
//日期类不涉及资源申请,因此不用自行定义析构函数、拷贝构造、赋值重载,使用过程中编译器直接调用编译器生成的默认成员函数即可
//打印日期
void Print();
//==运算符重载
bool operator==(const Date& d);
//<运算符重载
bool operator<(const Date& d);
//>运算符重载
bool operator>(const Date& d);
//>=运算符重载
bool operator>=(const Date& d);
//<=运算符重载
bool operator<=(const Date& d);
//!=运算符重载
bool operator!=(const Date& d);
//=运算符重载
// Date& operator=(const Date& d);//未涉及资源申请,可以使用编译器默认生成的赋值重载
//+=运算符重载,日期+天数 返回日期,改变原日期
Date& operator+=(int day);
//+运算符重载,日期+天数 返回日期,不改变原日期
Date operator+(int day);
//日期+日期没什么意义,故不实现
//-=运算符重载,日期-天数 返回日期,改变原日期
Date& operator-=(int day);
//-运算符重载,日期-天数 返回日期,不改变原日期
Date operator-(int day);
// 日期-日期 返回天数,与上面的:日期-天数,构成函数重载
int operator-(const Date& d);
//前置++ 运算符重载
Date& operator++();
//后置++ 运算符重载,为了与前置++的运算符重载区分,加了一个参数int(int只是为了区分,无实际意义)
Date operator++(int);
//前置-- 运算符重载
Date& operator--();
//后置-- 运算符重载,为了与前置--的运算符重载区分,加了一个参数int(int只是为了区分,无实际意义)
Date operator--(int);
private:
int _year = 2023;//C++中内置类型参数可以带默认值
int _month = 1;
int _day = 1;
};
源文件Date.cpp:
首先记得包含头文件Date.h,其次定义函数时要带上其类域。
构造函数:Date()
//Date.cpp
Date::Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
打印函数:Print()
//Date.cpp
void Date::Print()
{
cout << _year<<"/" << _month<<"/" << _day << endl;
}
//Test.cpp
void DateTest1()
{
Date d1;
d1.Print();
}
测试结果如下:

定义一个类对象d1,首先会调用默认构造函数(全缺省参数),将d1初始化,并打印打印,代码正确。
==重载:operator==()
//Date.cpp
bool Date::operator==(const Date& d)
{
return _year == d._year &&
_month == d._month &&
_day == d._day;
}
//Test.cpp
void DateTest2()
{
Date d1;
d1.Print();
Date d2(2023, 1, 1);
d2.Print();
Date d3(2022, 5, 4);
d3.Print();
Date d4(2023, 2, 1);
d4.Print();
cout << (d1 == d2) << endl;
cout << (d1 == d3) << endl;
cout << (d1 == d4) << endl;
}
测试结果如下:

bool返回值为1代表为真,返回值为0,代表为假,d1与d2相等,d1与d3和d4均不相等,代码正确。
<重载:operator<()
//Date.cpp
bool Date::operator<(const Date& d)
{
if (_year < d._year)
{
return true;
}
else if (_year==d._year&&_month < d._month)
{
return true;
}
else if (_year == d._year && _month ==d._month&&_day > d._day)
{
return true;
}
else
return false;
}
//也可以写成以下形式,但不推荐,上面的形式可读性更强
//bool Date::operator<(const Date& d)
//{
// return _year < d._year
// || (_year == d._year && _month < d._month)
// || (_year == d._year && _month == d._month && _day < d._day);
//}
//Test.cpp
void DateTest3()
{
Date d1;
d1.Print();
Date d2(2023, 1, 1);
d2.Print();
Date d3(2022, 5, 4);
d3.Print();
Date d4(2023, 2, 1);
d4.Print();
cout << (d1 < d2) << endl;
cout << (d1 < d3) << endl;
cout << (d1 < d4) << endl;
}
测试结果如下:

>重载:operator>()
//Date.cpp
bool Date::operator>(const Date& d)
{
//还用不用下面与operator<()类似的方法定义,其实只要等于或小于,就不大于,及不小于又不等于,就大于
//利用此逻辑我们可以复用已经定义的operator<()和operator==(),来定义operator>()
/*if (_year > d._year)
{
return true;
}
else if (_year == d._year && _month > d._month)
{
return true;
}
else if (_year == d._year && _month == d._month && _day > d._day)
{
return true;
}
else
return false;*/
return !(*this == d || *this < d);
}
//Test.cpp
void DateTest4()
{
Date d1;
d1.Print();
Date d2(2023, 1, 1);
d2.Print();
Date d3(2022, 5, 4);
d3.Print();
Date d4(2023, 2, 1);
d4.Print();
cout << (d1 > d2) << endl;
cout << (d1 > d3) << endl;
cout << (d1 > d4) << endl;
}
测试结果如下:

>=;<=;!=重载:operator>=();operator<=();operator!=()
//Date.cpp
bool Date::operator<=(const Date& d)
{
return !(*this > d);
}
bool Date::operator>=(const Date& d)
{
return !(*this < d);
}
bool Date::operator!=(const Date& d)
{
return !(*this == d);
}
//Test.cpp
void DateTest5()
{
Date d1;
d1.Print();
Date d2(2023, 1, 1);
d2.Print();
Date d3(2022, 5, 4);
d3.Print();
Date d4(2023, 2, 1);
d4.Print();
cout << (d1 <= d2) << endl;
cout << (d1 <= d3) << endl;
cout << (d1 <= d4) << endl;
cout << (d1 >= d2) << endl;
cout << (d1 >= d3) << endl;
cout << (d1 >= d4) << endl;
cout << (d1 != d2) << endl;
cout << (d1 != d3) << endl;
cout << (d1 != d4) << endl;
}
测试结果如下:

+=重载:operator+=()
//Date.cpp
//每月天数不同,而闰年与平年2月天数又不同,因此单独写一个函数还获取某年某月的天数
int Date::GetMonthDay()
{
assert(_month > 0 && _month < 13);
int array[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//array[0]=0方便返回
//判断闰年的条件是:1.四年一闰,百年不闰;2.四百年一闰
if (_month==2&&((_year % 4 == 0 && _year % 100 != 0) || (_year % 400 == 0)))
{
array[2] = 29;
}
return array[_month];
}
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetMonthDay())
{
_day -= GetMonthDay();
_month++;
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
//Test.cpp
void DateTest6()
{
Date d1;
d1.Print();
d1.operator+=(5000);//数字大一点可检查闰年判断是否有问题
d1.Print();
}
测试结果如下:

这里的+=重载会改变原日期,若不想改变原日期,我们该怎么实现,见下一部分。
+重载:operator+()
//Date.cpp
Date Date::operator+(int day)
{
//不改变d1本身就是不改变*this,那我们就要定义一个变量,把*this赋值给它,后续此变量的改变不影响*this
/*Date tmp = *this;
tmp._day += day;
while (tmp._day > tmp.GetMonthDay())
{
tmp._day -= tmp.GetMonthDay();
tmp._month++;
if (tmp._month == 13)
{
tmp._month = 1;
tmp._year++;
}
}
return tmp;*/
Date tmp=*this;
tmp += day;//由于我们已经实现了operator+=(),因此可以直接复用
return tmp;
}
//Test.cpp
void DateTest7()
{
Date d1;
d1.Print();
Date d2;
d2.Print();
d2=d1.operator+(5000);
d1.Print();
d2.Print();
}

//其实我们还可以在实现operator+=()时复用operator+(),同样运行正常,代码如下:
//但并不推荐是用这种方式,因为此种方式会多次调用拷贝构造,导致效率降低
Date Date::operator+(int day)
{
Date tmp = *this;
tmp._day += day;
while (tmp._day > tmp.GetMonthDay())
{
tmp._day -= tmp.GetMonthDay();
tmp._month++;
if (tmp._month == 13)
{
tmp._month = 1;
tmp._year++;
}
}
return tmp;
}
Date& Date::operator+=(int day)
{
/*_day += day;
while (_day > GetMonthDay())
{
_day -= GetMonthDay();
_month++;
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;*/
*this=*this+day;
return *this;
}
到此,下面的各成员函数都只提供代码,运行结果可自行拷贝查看。
-=重载:operator-=()
//Date.cpp
Date& Date::operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay();
}
return *this;
}
//Test.cpp
void DateTest8()
{
Date d1;
d1.Print();
d1-=5000;
d1.Print();
}
-重载:operator-()
//Date.cpp
//日期-天数 返回日期,不改变原日期
Date Date::operator-(int day)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
// 日期-日期 返回天数,与上面的:日期-天数,构成函数重载
int Date::operator-(const Date& d)
{
Date max = *this;
Date min = d;
int flag = 1;
if (max < min)
{
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (max != min)
{
min++;
n++;
}
return flag * n;
}
//Test.cpp
void DateTest9()
{
Date d1;
d1.Print();
Date d2;
d2.Print();
d2 = d1-5000;
d1.Print();
d2.Print();
}
void DateTest14()
{
Date d1(2013,12,4);
d1.Print();
Date d2(2025,1,6);
d2.Print();
cout << d1 - d2 << endl;
cout << d2 - d1 << endl;
}
前置++,后置++,前置--,后置--重载:operator++(),operator++(int),operator--(),operator++(int)
//Date.cpp
Date& Date::operator++()
{
*this += 1;//复用+=重载
return *this;
}
Date Date::operator++(int)//后置++ 重载,为了与前置++的运算符重载区分,加了一个参数int(int只是为了区分,无实际意义)
{
Date tmp(*this);
*this+=1;//复用+=重载
return tmp;
}
Date& Date::operator--()
{
*this -= 1;//复用-=重载
return *this;
}
Date Date::operator--(int)//后置-- 重载,为了与前置++的运算符重载区分,加了一个参数int(int只是为了区分,无实际意义)
{
Date tmp(*this);
*this-=1;//复用-=重载
return tmp;
}
//Test.cpp
void DateTest10()
{
Date d1;
d1.Print();
Date d2;
d2.Print();
d2=++d1;
d1.Print();
d2.Print();
}
void DateTest11()
{
Date d1;
d1.Print();
Date d2;
d2.Print();
d2 = d1++;
d1.Print();
d2.Print();
}
void DateTest12()
{
Date d1;
d1.Print();
Date d2;
d2.Print();
d2 = --d1;
d1.Print();
d2.Print();
}
void DateTest13()
{
Date d1;
d1.Print();
Date d2;
d2.Print();
d2 = d1--;
d1.Print();
d2.Print();
}
拓展:
我们之前学过cout,说cout是流插入的关键字,它具有自动判断类型然后输出的特质,那么,我们来看下面一段代码:
void DateTest15()
{
Date d1(2013, 12, 4);
d1.Print();
cout << d1;
}
//这段代码是编译不过的,此时 d1.Print();正常,而 cout << d1;会报错
//二元“<<”: 没有找到接受“Date”类型的右操作数的运算符(或没有可接受的转换)
为什么会出现以上这样的情况?实际上cout是一个定义在ostream库里的类,这里的cout<<(操作数)实际上是<<的运算符重载,只是在ostream库里的类里并未对参数类型为自定义类型的operator<<( )重载函数进行定义,此时我们只要利用学习的运算符重载的知识进行定义,就可以使用,定义方法有两种,一种是定义在当前类里面,另一种是定义为全局函数,但我们更推荐定义在全局,原因如下:
//若要定义在类里面,代码如下:
//Date.cpp
ostream& Date::operator<<(ostream& out)
{
out << _year << "/" << _month << "/" << _day << endl;
return out;
}
//Test.cpp
void DateTest15()
{
Date d1(2013, 12, 4);
d1.Print();
//cout<<d1//这条代码仍然不能编译通过,需要写成下面这种形式
d1 << cout;//为什么只有写成这种形式才能编译通过呢?
}
//根据前面所学我们知道,类中的成员函数有一个隐含的默认参数*this,这个*this通常被认为是操作符的第一个操作数,若将operator<<()定义在Date类里,那么就要保证第一个参数类型为Date,第二个参数类型是ostream&,那我们在使用的时候就只能用d1 << cout的形式,虽然也能编译成功,但是和我们内置类型用法有别,不好统一,因此我们一般不将其定义在类里,而是定义在全局。
//将其定义在全局,定义时就可以控制两参数的位置,从而达到与内置类型统一使用方法
//Date.cpp
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "/" << d._month << "/" <<d. _day << endl;
//类外不能访问类里加了private权限的成员变量,有两种解决办法:
//1.把private权限变为public(不推荐);
//2.在类中将其定义为友元函数, friend ostream& operator<<(ostream& out, const Date& d);
return out;
}
//Test.cpp
void DateTest15()
{
Date d1(2013, 12, 4);
d1.Print();
cout<<d1;//此时就可以按照与内置类型同样的方法使用cout了
}
测试结果如下:

实现了cout<<的运算符重载,cin>>的也是相同的道理,如下:
//Date.cpp
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day ;
return in;
}
//Test.cpp
void DateTest16()
{
Date d1;
cin >> d1;
cout << d1;
}
测试结果如下:

关于友元函数的用法,在类与对象篇有总结,有兴趣的可以查阅使用。
本篇所涉及的所有代码均整理到了以下链接,如有需要,欢迎参考指正!