目录
前言
前面我们介绍了C++中类和对象的相关知识和六个默认成员函数,在此基础上我们可以用C++实现一个日期类,这样可以帮助我们更加深入理解C++中的知识点,如果文章中有不懂的可以参考之前的文章
日期类的实现
我们在创建一个项目之前首先要知道这个项目要完成什么功能,以日期类为例,我们要实现日期的比较,日期的加减,以及日期的获取等功能,所以根据之前学的运算符重载等方面的知识我们可以很轻松地在Date.h文件中声明这个类所要实现的各种功能,如下所示
cpp
#pragma once
#include<iostream>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1);
int GetMonthDay(int year, int month)
{
int GetMonthDayArry[13] = { 0,31,30,31,30,31,30,31,31,30,31,30,31 };
if (2 == month && (year % 4 == 0 && year % 100 != 0 )|| year % 400 == 0)
{
return 29;
}
else
{
return GetMonthDayArry[month];
}
}
bool CheckDate();
void Print()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;
bool operator!=(const Date& d)const;
Date& operator+=(int day);
Date operator+(int day)const;
Date& operator-=(int day);
int operator-(const Date& d)const;
Date& operator++();
Date operator++(int);
Date& operator--();
Date operator--(int);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
下面我将逐一分析实现相关的功能。
日期的获取
我们创建好一个日期类后,首先要获取里面的日期,于是我们创建了一个GetMonthDay的成员函数,可以看到,它的返回值是int,在函数里面我们创建了一个名为GetMonthDayArry的数组,这个数组里面存放了每个月的天数,为了方便通过传月份来获取日期我们给这个数组的数据个数定为13,但是我们此时面临一个问题:遇到闰年的2月该怎么办呢?那么此时我们就要特殊情况特殊处理,闰年的判断规则是:四年一润,百年不润,四百年再润,于是我们便可以轻松写下如下代码:
cpp
int GetMonthDay(int year, int month)
{
int GetMonthDayArry[13] = { 0,31,30,31,30,31,30,31,31,30,31,30,31 };
if (2 == month && (year % 4 == 0 && year % 100 != 0 )|| year % 400 == 0)
{
return 29;
}
else
{
return GetMonthDayArry[month];
}
}
日期的比较
通过运算符重载的相关知识我们知道,作为类的成员函数重载时,有一个隐藏的this指针,因此我们只需要显示传一个参数,因此我们就有了以下定义:
cpp
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;
由于C++的语法,我们在实现上述逻辑时只需要实现其中的两个,其他的直接复用就可以轻松完成以上任务,我们在这里只实现小于和等于两个逻辑,其余直接复用即可,请参考以下代码:
cpp
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)
{
if (_day < d._day)
{
return true;
}
}
}
return false;
}
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 || *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);
}
const成员函数
我们发现,在以上几个日期的比较函数的后面我们都加上了const,这是为什么呢? 实际上我们将成员函数后面加const的函数称为const成员函数,从表面上看const是在修饰类的成员函数,实际上是在修饰该成员函数隐藏的this指针,表明在该成员函数中不能对类的任何成员进行修改,即:只要不修改成员的就可以在后面加上const
日期的加减
日期的加等
我们首先来介绍日期的加等,日期的加等原则就是进位法,即:将天数全加上去,如果超过该月的天数就将该月的天数减去,此时月数进一,如果超过12个月那么年数进一,于是我们就有了以下代码:
cpp
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
注意:我们看到,此时我们是用引用返回 ,在前面我们知道,返回对象生命周期到了会析构我们就传值返回;返回对象生命周期到了不会析构我们就传引用返回。而此时我们要获取的是加完之后的值,因此我们要传引用返回
日期的减等
日期减等的逻辑与加等类似,即:将天数全部减去后,如果天数小于0那么就将前一个月的天数加上去,如果月份为0那么就置为12,此时年份退一,于是我们便有了以下代码:
cpp
Date& Date::operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
_month == 12;
--_year;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
日期的加减
下面我将来实现日期的加减,这里的加减指:不改变原有日期,在加或减相关日期后,返回我们所需要的日期,此时由于出了作用域要析构,所以是用传值返回,同时由于我们前面实现了加等与减等,在这里我们只需要复用上述逻辑即可轻松完成,代码如下:
cpp
Date Date::operator+(int day)const
{
Date tmp = *this;
tmp += day;
return tmp;
}
Date Date::operator-(int day)const
{
Date tmp = *this;
tmp -= day;
return tmp;
}
日期的加加减减
由于函数重载的规则,我们在重载++或--操作符时,为了区分是前置还是后置,C++规定:后置++或后置--多了一个int的操作数加以区分。由于前置++或前置--返回的是++或--之后的值,因此我们要传引用返回;而后置则返回之前的值,所以传值返回,于是我们可以写下以下代码:
cpp
//前置++
Date& Date::operator++()
{
return *this += 1;
}
//后置++
Date Date::operator++(int)
{
Date tmp = *this;
return tmp += 1;
}
//前置--
Date& Date::operator--()
{
return *this -= 1;
}
//后置--
Date Date::operator--(int)
{
Date tmp = *this;
return tmp -= 1;
}
日期的相减
日期的相减返回的是两日期之前的差值,所以用int返回,我们可以写下以下代码:
cpp
int Date::operator-(const Date& d)const
{
Date max = *this;
Date min = d;
int flag = 1;
int n = 0;
if (*this < d)
{
max = d;
min = *this;
}
while (min != max)
{
++min;
++n;
}
return n * flag;
}
至此,一个完整的日期类就呈现了出来。
流插入和提取的重载
但是我们在Date.h文件中还有以下两个没有实现,那这两个是什么东西呢?
cpp
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
实际上,这是流插入和流提取操作符的重载,那么为什么要用流插入和流提取呢?实际上是因为:C++中的流插入和提取存在缺陷。我们发现与C语言中的printf和scanf不同的是,cout和cin可以自动识别变量的类型,但这仅仅只能识别内置类型,自定义类型的输入和输出时不能直接使用cout和cin,那么我们该怎么解决这个问题呢?于是C++语法上支持了<<和>>的重载。但是为什么我们选择将其定义在类的外面呢?实际上是为了可读性所采取的操作,但是我们将其定义在类外无法访问私有,为了解决这个问题我们引入了友元这个概念。
友元
什么是友元呢?友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜过多使用。
我们还是回归上述问题,为什么将其重载为成员函数会降低可读性呢?我们现在尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数。因为cout的输出流对 象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用 中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成全局函数。但又会导致类外没办 法访问成员,此时就需要友元来解决。operator>>同理。
友元的特点
1.友元函数可访问类的私有和保护成员,但不是类的成员函数 友元函数不能用const修饰
2.友元函数可以在类定义的任何地方声明,不受类访问限定符限制
3.一个函数可以是多个类的友元函数
4.友元函数的调用与普通函数的调用原理相同
在有了上述知识的铺垫后,我们可以将流插入流提取操作符的重载函数写出,代码如下:
cpp
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
cout << "请依次输入年月日:";
in >> d._year >> d._month >> d._day;
return in;
}
日期类代码
整个Date.c的代码如下:
cpp
bool Date::CheckDate()
{
if (_month < 1 || _month>12
||_day<1||_day>GetMonthDay(_year,_month))
{
return false;
}
else
{
return true;
}
}
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
if (!CheckDate())
{
cout << "日期错误" << endl;
}
}
void Date::Print()const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
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)
{
if (_day < d._day)
{
return true;
}
}
}
return false;
}
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 || *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);
}
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month > 12)
{
_month = 1;
++_year;
}
}
return *this;
}
Date Date::operator+(int day)const
{
Date tmp = *this;
tmp += day;
return tmp;
}
Date& Date::operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
--_month;
_day += GetMonthDay(_year, _month);
if (_month == 0)
{
_month = 12;
--_year;
}
}
return *this;
}
int Date::operator-(const Date& d)const
{
Date max = *this;
Date min = d;
int flag = 1;
int n = 0;
if (*this < d)
{
max = d;
min = *this;
}
while (min != max)
{
++min;
++n;
}
return n * flag;
}
Date& Date::operator++()
{
return *this += 1;
}
Date Date::operator++(int)
{
Date tmp = *this;
return tmp += 1;
}
Date& Date::operator--()
{
return *this -= 1;
}
Date Date::operator--(int)
{
Date tmp = *this;
return tmp -= 1;
}
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
cout << "请依次输入年月日:";
in >> d._year >> d._month >> d._day;
return in;
}
总结
感谢您能在百忙之中抽出时间来看鄙人的文章,相信看完文章的你会对C++的类与对象有一个更清楚的认识,受限于本人的水平,可能文章中会存在一些问题,欢迎各位指正,如果本篇文章对您有所帮助的话,希望能给我点赞评论加关注,您的支持就是对我创作的最大鼓励。