附:日期类Date的实现
ʕ • ᴥ • ʔ
づ♡ど
🎉 欢迎点赞支持🎉
个人主页:励志不掉头发的内向程序员;
专栏主页:C++语言;
文章目录
前言
一、日期类的框架
二、构造函数
三、析构函数
四、拷贝构造
五、运算符重载
1、operator+=(int day)
2、operator+(int day)
3、operator-=(int day)
4、operator-(int day
5、operator-(const Date& d)
6、比较运算符
7、operator++/--
8、operator<>
总结
前言
我们本章节主要就是使用之前的学的的C++知识来制作一个日期类,这个代码的制作可以很好的让我们加深对类和对象的理解,让我们一起来看看吧。
一、日期类的框架
我们类的基本框架比较简单
class Date
{
public:private:int _year;int _month;int _day;
};
这就是我们的日期类的基本框架,里面包含了三个成员变量,我们在此框架中一点一点的填充内容以实现类的各项功能。
二、构造函数
Date::Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
我们的构造函数,也是默认构造函数,因为不用传参。
三、析构函数
Date::~Date(){_year = _month = _day = 0;}
我们从我们的构造函数就可以看出来,我们其实是没有创建什么对象去指向自己的资源的,都是内置类型的函数,所以我们其实完全就没有必要写析构函数,但是为了代码的完整性,这里还是决定写一写。
四、拷贝构造
Date::Date(const Date& d){_year = d._year;_month = d._month;_day = d._month;}
拷贝构造同理,我们也没有必要实现。注意我们拷贝构造的传参时要传引用,不然就会产生无穷递归。
五、运算符重载
我们运算符重载得对有意义的运算符进行重载,那哪些运算符有意义呢
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+(int day);Date& operator+=(int day);Date& operator-(const Date& d);Date& operator-=(const Date& d);Date operator-(int day);Date& operator-=(int day);
其次就是这些加减运算符,像加几天是什么日期,减几天是什么日期,什么日期到日期之间有多少天等。
我们来逐一实现
1、operator+=(int day)
我们如果想要加几天,那我们就的考虑那个月有多少天的问题,因为如果天加到了超过了那一个月的上限,那就得到下一个月去了,所以我们最好创建一个数组去记录我们的每个月有多少天。
int GetMonthDay(int year, int month){// 防止月份有误assert(month > 0 && month < 13);// 因为数组得经常使用,使用变成静态数组// 数组第0位是-1方便代码操作,因为月份数就对应数组下标了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];}}
此时当我们拥有了这个数组,我们就可以知道天数超过多少就得向月份进位了。此时我们再来实现这个+的功能就简单很多了。
Date& Date::operator+=(int day)
{_day += day;while (GetMonthDay(_year, _month) < _day){_day -= GetMonthDay(_year, _month);_month++;if (_month > 12){_month = 1;_year++;}}return *this;
}
这串代码也是比较好理解的,就是如果我们的天数大于那个月的天数最大值了我们就向月份进一后减去那个月份的天数,如果月份到达13了就向年份进一,一直到我们的天数不超过那一月的最大值循环就退出了。
2、operator+(int day)
我们在已经实现了+=的情况下,我们可以利用我们的+=来实现我们的+,这种操作叫做函数复用
Date Date::operator+(int day)
{Date tmp = *this;tmp += day;return tmp;
}
同时我们发现,我们的这两个+全是传引用返回的,这是因为这样可以减少几次拷贝构造来提高效率。
3、operator-=(int day)
Date& Date::operator-=(int day)
{_day -= day;while (_day <= 0){--_month;if (_month == 0){_month = 12;_year--;}_day += GetMonthDay(_year, _month);}return *this;
}
我们可以把我们的天数先减成负数后再进行减少月份来把天数加成正数。
4、operator-(int day)
Date Date::operator-(int day)
{Date tmp = *this;tmp -= day;return tmp;
}
5、operator-(const Date& d)
这个的主要作用就是看看我们的日期之间总共相差多少天,但是如果我们就是日期相减,虽然也可以,但是代码比较复杂,我们可以换一个思路,那就是找到我们的比较小的日期,然后再拿我们的小日期一直++,知道我们的小日期和大日期相等,我们拿个变量记录加的次数,这样就可以轻松实现了。而且这些操作我们之前就有了,只需要函数复用即可了。
int Date::operator-(const Date& d)
{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;
}
6、比较运算符
我们一次性来看看我们的比较运算符,因为这个比较简单,我们就直接看代码吧
<:
bool Date::operator<(const Date& d)
{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)
{return _year == d._year && _month == d._month && _day == d._day;
}
<=:
bool Date::operator<=(const Date& d)
{// 复用<和==return *this < d || *this == d;
}
>:
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);
}
我们这个地方使用了大量的函数复用,大家可以认真观察。
7、operator++/--
我们都知道,我们的++/--有前置++/--和后置++/--,但是它们都是同一个operator函数,而且它们都是一元的,那我们怎么区分呢,虽然在上一章节就有提到过,但是没有仔细说明,我们这里借助我们的Date类来说明一下。
我们这里的解决办法是我们的后置++/--在前置++/--的基础上增加一个形参int(只需要增加一个int去区分就行,不用写形参名字),这样就可以和前置++/--构成重载了,方便区分。为什么是后置++/--增加形参那是因为前置++/--经常使用,它比后置++/--少几次拷贝构造,可以传引用返回,但是后置++/--不行,我们来看看它们的代码。
//d++
Date Date::operator++(int)
{Date tmp = *this;*this += 1;return tmp;
}//++d
Date& Date::operator++()
{return *this += 1;
}//d--
Date Date::operator--(int)
{Date tmp = *this;*this -= 1;return tmp;
}//--d
Date& Date::operator--()
{return *this -= 1;
}
我们可以看到我们的后置++/--因为要先使用后++/--,所以要创建一个临时变量去给我们的程序去使用,这样就会多几次拷贝构造。
8、operator<</>>
我们的类对象如果想要使用流输入/输出也是需要进行重载的,cin/cout只支持内置类型。所以我们来看看我们怎么重载流输入和输出,我们的流输入(cin)和输出(cout)的返回值分别对应的是istream/ostream当然用void也可以简单替代。首先我们先看看我们输出的简单一点的函数形式
void Date::operator<<(ostream& out);
这是我们写在类内时的写法,我们可能学的好的会发现问题,那就是我们调用时是这样调用的
cout << *****;
按道理说参数应该先是ostream,再是Date啊,但是类内默认this指针是第一啊。确实是这样,所以如果这样写我们就只能这样调用
d.operator<<(cout);
// 或者
d << cout
这样简直倒反天罡,为了避免这种事情发生,我们必须想办法让这两个参数位置调换,所以我们不能让我们的<<重载变成成员函数,得把它写出来变成全局函数
ostream operator<<(ostream out, const Date& d)
{out << d._year << "日" << d._month << "月" << d._day << "日" << endl;
}
这样写我们就可以把上面的位置调换过来,但是又产生了一个新的问题那就是我们访问不了私有了,也就是上面的那种形式是会报错的,我们之前讲解了如何使我们的外界函数访问私有,在这里在说明一个,那就是友元函数,但是友元函数细讲是在后面,这里只是说一下怎么用。
只要在我们想要访问的类中加一个函数的友元声明我们便可在此函数中访问该类的私有
class Date
{// 友元声明friend void Func();
public:
private:
}void Func()
{
}
友元声明就是函数声明前加一个friend即可,可以加在类的任意位置,这样我们的Func即可访问类中的私有了。
此时还有一个问题,那就是不能连续的输出我们的数据
cout << *** << *** << ....;
我们上面的函数是无法做到这一点的,原因是因为我们的返回值是void而非ostream,我们的输出输入操作符是从左往右结合,与其他运算符相反,也就是我们cout和下一个数据结合后返回值是void,这样就不能继续结合了,但是如果返回值是ostream,那便可以继续往后结合,所以我们最终的写法应该是
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "日" << d._month << "月" << d._day << "日" << endl;return out;
}
我们的输入也是相同的写法,大家自己去试试吧。
注:我们istream和ostream必须是传引用,因为它们的拷贝构造被封了,无法调用,所以必须引用,不然会报错。
总结
以上就是我们日期类的写法,希望大家能够对我们的类的理解更加的深入。
🎇坚持到这里已经很厉害啦,辛苦啦🎇
ʕ • ᴥ • ʔ
づ♡ど