类和对象1(初识)
类和对象2(默认成员函数)
类和对象3(初始化列表)
类和对象4(static、const、友元)
在了解了类和对象的基础知识后,我们就可以简单实现一个日期类(前期的知识介绍链接如上,一些基础知识本文就不过多赘述):
我们可以先来看一下网络上找到的日期计算器的功能:
可以实现计算两个日期之差,日期加减天数等。
在学习类和对象之前,我们会将日期的年月日存储在结构体中,然后通过封装函数来实现对这个日期结构体的操作;
但是现在,我们就可以将日期封装为类类型的属性,对日期类的操作封装为类的方法,以方便我们使用。
当然,为了实现上面提到的两个功能,我们需要实现判段两个日期是否相等,比较两个日期等方法,当然也需要将日期打印出来的方法。接下来就会一一实现:
我们首先定义一个类类型:
class Date
{
private:
int _year;
int _month;
int _day;
};
首先来实现4个默认成员函数
这个日期类中只有内置类型,所以编译器生成的无参的默认成员函数不能实现其初始化。我们可以实现一个全缺省的默认构造函数,这样既可以不传参定义类对象,也可以传参:
// 全缺省的构造函数
Date::Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
在初始化列表中实现对成员变量的初始化。
日期类中其实并没有动态开辟的资源,所以编译器自动生成的析构函数就已经足够,但是这里还是实现一下,可以在析构函数中将成员变量置0:
// 析构函数
Date::~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
拷贝构造函数是构造函数的重载,参数为类对象的引用:
// 拷贝构造函数
// d2(d1)
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
使用const修饰参数,可以使const对象也可以作为参数对新的类对象赋值。
赋值运算符重载就是operator
对=
的函数重载。显式的参数为const Date&
即可以传const修饰的对象:
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
在函数中将该对象(this指针指向的)的成员变量赋值为d中的成员变量即可。
需要注意的是,为了实现连续赋值,这个函数的返回值为类类型的引用,返回*this
:
例如将几个内置类型变量连续赋值:
a = b = c = d;
在这样的代码中,就是d给c赋值的结果给b赋值,结果再给a赋值。
所以对于赋值运算符重载,这个函数返回的就应该是=前面的操作数,即第一个参数的引用,即返回*this
。
有了默认成员函数后,我们继续实现成员函数:
日期的比较可以实现如下的重载:==、!=、>、>=、<、<=
这些函数的参数列表应该都是相同的,都是一个隐式的this
指针与一个类的引用Date&
;
返回值都是bool,如果表达式成立就返回true,否则返回false。
但是这类函数中都不需要改变对象的值,所以可以对this指针指向的对象与类对象的引用都用const修饰:
//==重载举例:
bool operator==(const Date& d) const;
实现时,在函数中通过判断两个对象中的成员变量得出两个日期的大小关系了:
==运算符重载
// ==运算符重载
bool Date::operator==(const Date& d)const
{
if ((_year == d._year)
&& (_month == d._month)
&& (_day == d._day))
{
return true;
}
else
{
return false;
}
}
// !=运算符重载
bool Date::operator!=(const Date& d)const
{
if (!(*this == d))
{
return true;
}
return false;
}
// >运算符重载
bool Date::operator>(const Date& d)const
{
if (_year > d._year)
{
return true;
}
if (_year == d._year && _month > d._month)
{
return true;
}
if (_year == d._year && _month == d._month && _day > d._day)
{
return true;
}
return false;
}
// >=运算符重载
bool Date::operator>=(const Date& d)const
{
if (*this > d || *this == d)
{
return true;
}
return false;
}
// <运算符重载
bool Date::operator<(const Date& d)const
{
if (_year < d._year)
{
return true;
}
if (_year == d._year && _month < d._month)
{
return true;
}
if (_year == d._year && _month == d._month && _day < d._day)
{
return true;
}
return false;
}
// <=运算符重载
bool Date::operator<=(const Date& d)const
{
if (*this < d || *this == d)
{
return true;
}
return false;
}
日期+=天数,即计算该日期对象(隐式的this指向的)在某天数后的日期是哪一天,返回对象的引用。+=操作符,显然是会改变该日期对象的成员变量的,所以不能用const修饰:
Date& operator+=(int day);
在实现时,可以先给该日期对象的_day
成员加上day
;
当_day
的值大于当前_month
的值时,_day
减去当前月的天数,然后_month
加1(向后走一月);
若_month
的值大于12,则重置为1,_year
加1(向后走一年);
直到_day
的值小于当前月的天数为止;
最后返回*this
的引用。
我们可以再封装一个函数来获取某月的天数(通过数组的方式存储12个月的天数,返回对应月的天数即可):
// 获取某年某月的天数(用于日期加减天数)
int Date::GetMonthDay(int year, int month)
{
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)))
{
monthdays[month] = 29;
}
return monthdays[month];
}
然后就可以实现+=运算符重载:
需要注意的是,传参的天数day可以为负数,即表示该日期减-day,此时调用嗲用-=即可(马上就会实现)
// 日期+=天数
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 = 1;
}
}
return *this;
}
日期+天数,是不能修改该日期类的日期的,所以可以为this指针指向的对象加const修饰:
Date operator+(int day)const;
在函数中,创建一个临时日期类对象temp
,并用*this
对其初始化(拷贝构造);
然后对temp+=day;
,即调用上面实现的+=
重载;
最后返回temp
。
需要注意的是,这里的temp在出作用域后就会销毁,所以这里的返回值必须为值,而非返回引用(虽然传值返回会调拷贝构造)
// 日期+天数
Date Date::operator+(int day)const
{
Date temp(*this);//拷贝构造
temp += day;//复用+=运算符重载
return temp;//返回临时值,不改变*this的值
}
日期-=天数的实现与+=类似:
先给_day
减去day
;
如果减去day
后_day
的值小于0,则_day
加上当前月的上一个月(_month-1
)的天数;
若_month
的值小于0,_month
置为12,_year
的值减1;
直到_day
的值大于0为止;
最后返回*this
的引用。
// 日期-=天数
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day < 1)
{
_month--;
_day += GetMonthDay(_year, _month);//如果--后的_month为0,函数返回0,不影响结果
if (_month <= 0)
{
_year--;
_month = 13;
}
}
return *this;
}
在实现时需要注意的是:
当_month
的值为1时,_day
要加上(_month-1
)月的天数,此时传给GetMonthDay
的就是0,但是由于我们在实现GetMonthDay
时,月份数组中下标为0的数据为0,_day-0
对_day
没有影响,所以不影响最终的结果;
当_day
小于0时,就调用+= -day
的方式来解决即可。
日期-天数的实现与+类似,需要创建临时对象temp
,并且必须返回值而不能返回引用:
// 日期-天数
Date Date::operator-(int day)const
{
Date temp;
temp -= day;
return temp;
}
++
就是+=1
,这很容易理解,所以在这个函数中,只需要调用上面重载来+=1
即可。
需要注意的是,前置++与后置++的区别:前置++在+=1后,表达式的值为+1后的值;而后置++在+=1后,表达式的值为+1前的值。
在++中,操作数就是类对象,前置++版本重载的返回值应该为+1后的引用;后置版本的返回值应该为对象在递增前的值。
我们只需要想办法区分operator的参数列表来实现重载即可:
对于两个操作数的操作符运算符重载,函数的第一个参数对应左操作数,第二个参数对应右操作数。例如==的重载:bool operator==(const Date& d);
的第一个参数为隐式的this
,第二个参数为const Date& d
。调用这个重载函数时可以:d1 == d2;
这个调用就相当于d1.operator==(d2);
。
前置++可以只隐式的传递this指针作为第一个参数,它就对应着前置++的操作数;
由于返回递增后的日期对象,所以返回对象的引用即可:
// 前置++
Date& Date::operator++()
{
*this += 1;
return *this;
}
我们在使用前置++时,就相当于调用这个重载函数:d.operator++();
后置++的实现为了与前置++区分,参数列表中除了隐式的this指针,还可以有一个不被使用的int。*this对应着后置++的第一个操作数,int对应着第二个操作数(但是这里的int只做区分意义,并不使用);
由于返回递增前的日期对象,所以需要创建临时对象temp,用 *this 初始化,原对象递增后返回temp即可。这里需要值返回而非引用返回(原因与前面一致):
// 后置++
Date Date::operator++(int)
{
Date temp(*this);
*this += 1;
return temp;
}
使用后置++时,就相当于调用这个重载函数:d.operator++(0);
前置 - - 与后置 - - 的实现与区分思路与++类似,在函数中使用-=,并且以额外的int形参区分:
// 前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
引用返回递减后的对象,使用时相当于:d.operator--();
// 后置--
Date Date::operator--(int)
{
Date temp(*this);
*this -= 1;
return *this;
}
值返回递减前的对象,使用时相当于:d.operator--(0);
日期 - 日期即重载-
,返回两个日期之间相差多的天数。
参数列表有两个,即隐式的this指针,与类对象的引用。由于在进行计算两日期之间天数时不会改变日期对象,所以对 *this 与对象的引用都用const修饰(与前面比较运算符重载时相同,这里不再赘述);
返回值为int,表示相差的天数:
int operator-(const Date& d)const;
实现这个函数时,我们可以直接通过小日期递增,并且记录递增的次数,知道两个日期相等,返回计数器变量的值。
需要注意的是:不能改变原对象的值,所以创建临时对象temp来递增计数:
// 日期-日期 返回天数
int Date::operator-(const Date& d)const
{
int day = 0;
Date temp(*this);
if (temp > d)
{
while (temp != d)
{
--temp;
day++;
}
}
else
{
while (temp != d)
{
++temp;
day--;
}
}
return day;
}
在实现了上面这些成员函数后,类对象的加、减、乘、除、比较等都可以像内置类型一样使用,大大提高了便利与可读性,那么能不能对输入与输出的运算符>>
、<<
也进行重载呢?当然是可以的:
cin
与cout
是标准输入与标准输出对象,使用cin >>
可以从标准输入流中读取数据;cout <<
可以将数据打印在标准输出流中。它们的类类型分别是istream
与ostream
。
有了这样的知识,我们只要将>>操作符左边的操作数对应到cin,右边的操作数对应到要输入的类对象,就可以实现>>的重载;同理,<<左边的操作数对应为cout,右边的操作数对应为要打印的类对象,就可以实现<<的重载。
如此,>>
重载的第一个参数类型应该为istream&
,第二个参数类型应为Date&
;<<
重载的第一个参数类型应为ostream&
,第二个参数类型应为const Date&
。
但是,对于类的成员函数,第一个参数一定是固定的隐式传参的this指针,这就导致了对于成员函数而言,不可能实现上面的传参方式。
要想实现上面的传参方式,就必须是非成员函数,并且要能在函数中访问成员变量。在这样的需求下,我们可以使用friend
来声明一个外部函数作为该类的友元函数,这样就可以既没有隐式的this,也能访问成员变量了;
为了实现连续的输入输出,表达式的值应为流,所以在实现重载时,函数的返回值为istream&
或ostream&
:
//>>重载
istream& operator>>(istream& in, Date& d)
{
int year = 0;
int month = 0;
int day = 0;
in >> year >> month >> day;
if (month < 1 || month>12 || day<1 || day>Date::GetMonthDay(year, month))
{
cout << "输入错误" << endl;
assert(0);
}
else
{
d._year = year;
d._month = month;
d._day = day;
}
return in;
}
//<<重载
ostream& operator<<(ostream& out, const Date& d)
{
if (d._month < 1 || d._month>12 || d._day<1 || d._day>Date::GetMonthDay(d._year, d._month))
{
cout << "输入错误" << endl;
}
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
当然还需要在类内部加上friend
声明。
到此,所有日期类的成员函数就实现完了,总的代码就在后面,我们也可以写一个程序来测试一下这个日期类。
//头文件
#include
#include
using namespace std;
class Date
{
//友元输入输出
friend istream& operator>>(istream& in, Date& d);
friend ostream& operator<<(ostream& out, const Date& d);
public:
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
// 拷贝构造函数
// d2(d1)
Date(const Date& d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d);
// 析构函数
~Date();
// 获取某年某月的天数
static int GetMonthDay(int year, int month);
// 日期+=天数
Date& operator+=(int day);
// 日期+天数
Date operator+(int day)const;
// 日期-=天数
Date& operator-=(int day);
// 日期-天数
Date operator-(int day)const;
// 前置++
Date& operator++();
// 后置++
Date operator++(int);
// 前置--
Date& operator--();
// 后置--
Date operator--(int);
// >运算符重载
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;
// 日期-日期 返回天数
int operator-(const Date& d)const;
//打印
void printDate()const;
private:
int _year;
int _month;
int _day;
};
istream& operator>>(istream& in, Date& d);
ostream& operator<<(ostream& out, const Date& d);
//源文件
#include"data.h"
// 全缺省的构造函数
Date::Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
// 拷贝构造函数
// d2(d1)
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
// 析构函数
Date::~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
// 获取某年某月的天数(用于日期加减天数)
int Date::GetMonthDay(int year, int month)
{
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)))
{
monthdays[month] = 29;
}
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 = 1;
}
}
return *this;
}
// 日期+天数
Date Date::operator+(int day)const
{
Date temp(*this);//拷贝构造
temp += day;//复用+=运算符重载
return temp;//返回临时值,不改变*this的值
}
// 日期-=天数
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day < 1)
{
_month--;
_day += GetMonthDay(_year, _month);//如果--后的_month为0,函数返回0,不影响结果
if (_month <= 0)
{
_year--;
_month = 13;
}
}
return *this;
}
// 日期-天数
Date Date::operator-(int day)const
{
Date temp;
temp -= day;
return temp;
}
// 前置++
Date& Date::operator++()
{
*this += 1;
return *this;
}
// 后置++
Date Date::operator++(int)
{
Date temp(*this);
*this += 1;
return temp;
}
// 前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
// 后置--
Date Date::operator--(int)
{
Date temp(*this);
*this -= 1;
return *this;
}
// ==运算符重载
bool Date::operator==(const Date& d)const
{
if ((_year == d._year)
&& (_month == d._month)
&& (_day == d._day))
{
return true;
}
else
{
return false;
}
}
// >运算符重载
bool Date::operator>(const Date& d)const
{
if (_year > d._year)
{
return true;
}
if (_year == d._year && _month > d._month)
{
return true;
}
if (_year == d._year && _month == d._month && _day > d._day)
{
return true;
}
return false;
}
// >=运算符重载
bool Date::operator>=(const Date& d)const
{
if (*this > d || *this == d)
{
return true;
}
return false;
}
// <运算符重载
bool Date::operator<(const Date& d)const
{
if (_year < d._year)
{
return true;
}
if (_year == d._year && _month < d._month)
{
return true;
}
if (_year == d._year && _month == d._month && _day < d._day)
{
return true;
}
return false;
}
// <=运算符重载
bool Date::operator<=(const Date& d)const
{
if (*this < d || *this == d)
{
return true;
}
return false;
}
// !=运算符重载
bool Date::operator!=(const Date& d)const
{
if (!(*this == d))
{
return true;
}
return false;
}
// 日期-日期 返回天数
int Date::operator-(const Date& d)const
{
int day = 0;
Date temp(*this);
if (temp > d)
{
while (temp != d)
{
--temp;
day++;
}
}
else
{
while (temp != d)
{
++temp;
day--;
}
}
return day;
}
//打印
void Date::printDate()const
{
cout << _year << " " << _month << " " << _day << endl;
}
istream& operator>>(istream& in, Date& d)
{
int year = 0;
int month = 0;
int day = 0;
in >> year >> month >> day;
if (month < 1 || month>12 || day<1 || day>Date::GetMonthDay(year, month))
{
cout << "输入错误" << endl;
assert(0);
}
else
{
d._year = year;
d._month = month;
d._day = day;
}
return in;
}
ostream& operator<<(ostream& out, const Date& d)
{
if (d._month < 1 || d._month>12 || d._day<1 || d._day>Date::GetMonthDay(d._year, d._month))
{
cout << "输入错误" << endl;
}
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
//main函数
int main()
{
Date d1;
cin >> d1;
cout << d1;
Date d2(2023, 5, 20);
cout << d2;
cout << d2 - d1 << "天" << endl;
return 0;
}
到此,关于日期类的实现就介绍完了。通过实现日期类,相信大家对类和对象有了更深的了解
同时,类和对象的基础知识也介绍完了
C++之旅还在继续,欢迎大家持续关注哦!!!
如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出
如果本文对你有帮助,希望一键三连哦
C++之路才刚刚开始,我们一起加油吧!