前言
在学习了类和对象的六大成员函数后,为了巩固我们学习的知识可以手写一个日期类来帮助我们理解类和对象,加深对于其的了解。
默认函数
构造函数
既然是写类和对象,我们首先就要定义一个类,然后根据实际需要来加入类的数据与函数。作为一个日期类,肯定要有时间了,我们采用公元纪年法,存储三个变量,年 月 日,如下。
class Date
{
private:
int _year;
int _month;
int _day;
};
注意这里将三个成员变量定义为private:,是为了防止用户在外面修改,提高安全性。其次这里在每个成员变量前面加了个下划线。 _year,这里是为了防止后面与函数的参数重名。当然重名也有其他解决方法例如用this.year,也可以。具体选那种看读者更适合那种编码风格。
写完上面的变量后,我们第一个想到要写的函数必定是构造函数了。如下函数
Date(int year=1900, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
注意我们这里写成了全缺省,这样做的好处是不用写无参的构造函数,给每个日期对象都有默认值。
拷贝构造函数
其次我们要写的就是拷贝构造函数。注意这里的形参加了个const,这里加了const一是为了方防止修改形参d,二是为了通用性。如下例子
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
假如我们没有加const,有下述程序。这段代码会报错!为什么呢?d1的类型为const Date,而拷贝构造函数参数类型为Date& d,如果传参成功,是不是就可以在构造函数的内部修改原本为const类型的变量,违背了基本的定义语法。但用一个const Date类型拷贝初始化是我们需要的场景,为了让其正常运行,就需要在拷贝构造函数加const。
int main()
{
const Date d1(2024, 4, 16);
Date d2(d1);
return 0;
}
有const变量自然也有普通的变量了。我们就可以写个普通变量的拷贝构造函数。
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
其实在很多地方会将上面两个构造函数简化为一个,即保留含const的
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
那自然有人问?普通变量的拷贝怎么办呢?在这里就不得不提C++对于变量类型的处理了。我们首先看段熟悉的代码。
int a=0;
a=1.12;
这段代码会报错么?为什么?大家可以在程序中运行下,结果是不会报错的,有的编译器可能会有警告。大家知道整型与浮点型在内存中的存储方式是不一样的,即使是对浮点型内存的截断读取a也不可能为1.
但a的结果却是一,这是因为编译器帮我们做了类型转化,称之为隐式转换。如下图。
同理当我们将Date变量赋给const Date& d,编译器也会额外开辟空间付给形参d。
或许有读者又有疑问?Date变量赋给const Date& d可以,为什么const Date变量赋给Date& d不可以,因为前者是将内存的权限放小,而后者是将对内存的权限放大。在C++中将权限放小可以,但把权限放大就有可能产生难以预料的后果。
接下来我们来实现与拷贝构造函数功能十分像的赋值重载函数。
赋值重载函数
代码如下
Date& operator=(const Date& d)
{
if (&d != this)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
这里我们任然将参数的类型加上const省去对于普通变量与const变量的分类了,当然对于普通变量会隐式转换,会减少些效率。
注意这里将d的地址与this比较,这是为了防止自己和自己赋值的情况如 a=a,这样没有任何的意义。
析构函数
在这个对象中我们没有开辟内存,没有在堆区申请空间,写不写析构函数都可以。
~Date()
{
}
成员函数
<<重载
我们为了后续的方便,首先要实现的便是cout输出Date类型,对于内置类型cout可以直接的输出,但是对于自定义类型要我们使用操作符重载.
按照习惯我们极大概率会将<<写在类里面。写出如下的代码
ostream& operator<<(ostream& out)
{
out << _year << "-" << _month << "-" << _day << endl;
return out;
}
其中的ostream参数是输出时要用到的一种类型,返回值为ostream是为了连续输出的原因。这个看起来没有什么错误,但运行的时候就会报错!
我们明明重载了<<操作符,为什么却提示我们没有匹配类型呢?这就不得不提到this了,在使用cout << d1操作符重载的时候,我们从左向右显然要传递两个参数ostream和Date,在类中的成员函数默认第一个参数传递Date,即形参this指针,第二个实参初始化函数的形参。
关于this指针详情可以看【C++ 类和对象 上 - CSDN App】http://t.csdnimg.cn/Wx5iO。在这里就不叙述了。
于是上面的代码也不是不可以用,可以采用如下的方法使用
但这种方法显然是不符合我们日常认知的。
为了解决这种问题,我们把<<操作符改为全局函数。就可以解决顺序的问题了。如下代码
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
但此时又有一个新的问题,这个重载函数不可以访问Date对象中私有的成员变量,这就体现了我们类的安全性高,为了解决我们需要把这个操作符重载函数声明为友元函数就可以了。
class Date
{
public:
Date(int year=1900, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)
{
if (&d != this)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
friend ostream& operator<<(ostream& out, const Date& d);
private:
int _year;
int _month;
int _day;
};
这样我们就可以正常输出了。
>>重载
有了输出,当然要有其对应的输入最好。和输出重载一样,将>>操作符重载为全局函数,并且在类中声明为友元函数。
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
如下图,刚开始输出1900时我们写的全缺省参数的作用,而后输出的就是我们输入的2024 5 13.
光有输入输出函数显然是不可以的,我们也要有对应的函数。
大小比较
对于一个日期比较大小是符合实际需求的,我们可以写个cmp成员,但更好的使用操作符重载,< >,这个我们最熟悉又可以减小记忆的负担。
>比较
我们有两种方法比较两个日期的大小。
方法一
不断地寻找true条件,最后剩下的就是false。注意年数相等的时候要判断月份
bool Date::operator>(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;
}
方法二
将日期的比较转换为数的比较。月份最多有12月,天最多有31天,我们就可以将年扩大10000倍,月扩大100倍,将2024年5月13日与2023年4月20日比较转换为2024513与2023420比较。我们知道数的比较大小是从高位往下开始比较的。这与我们比较日期的顺序不谋而合,就可以如下的简化代码。
bool Date::operator>(Date& d)
{
return _day + _month * 100 + _year * 10000 >
d._day + d._month * 100 + d._year * 10000;
}
同理小于等于。大于等于,小于都可以如上比较。
bool Date::operator<(Date& d)
{
return _day + _month * 100 + _year * 10000 <
d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator<=(Date& d)
{
return _day + _month * 100 + _year * 10000 <=
d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator>=(Date& d)
{
return _day + _month * 100 + _year * 10000 >=
d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator!=(Date& d)
{
return _day + _month * 100 + _year * 10000 !=
d._day + d._month * 100 + d._year * 10000;
}
判断两个日期是否相等的代码也十分简单,如下。
相等判断
bool Date::operator==(Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
到此我们的比较函数就写完了。但光有比较还不可以,我们可能想要知道50天后是哪一天,50天前是那一天,两个日期相差多少天。
加减操作
在后续的操作中我们不可避免的要访问某年某月有多少天,我们便可以将他封装为成员函数,便于我们查找天数。
获取天数
我们可以将每个月的天数写在一个数组中,然后哪一个月就读取哪一个数字,但其中2月十分特殊要分为润年的问题要单独判断下。
int Date::GetMonthDay(Date& d)
{
static int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (d._month == 2 && ((d._year % 400 == 0) || (d._year % 4 == 0 && d._year % 100 != 0)))
{
return 29;
}
return arr[d._month];
}
这里将数组加上static 是为了避免重复创建,提高效率。然后就是判断是不是闰年的二月。
+=重载
我们可以将一个日期类加上n天返回加n天后的日期。
Date& Date::operator+=(int t)
{
_day += t;
while (_day > GetMonthDay(*this))
{
_day -= GetMonthDay(*this);
_month++;
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
我们整体的循环是在找当前月合理的天数,如果不合理就月份加一,天数减去当前月,继续判断直到结束。
测试结果正确。
大家写的时候可以用日期计算器 - 天数计算器 | 在线日期计算工具 (sojson.com)这个网站检测。
+重载
我们之前已经写完+=了,先在写+的思路是不是与+=类似,我们可以仿照+=写出+重载,但是我们还有更加简单的方法,复用+=!!
Date Date::operator+(int t)
{
Date t1 = *this;
t1 += t;
return t1;
}
这样复用代码就大大简化了我们写代码的复杂度。其实上面的判断也可以复用代码,读者可以自行尝试。
-=重载
一个日期减去一个天数,与一个日前加上一个天数十分像。天数小于肯定是不合理的日期就加上当前月数。不断的循环判断直到合理数据。
Date& Date::operator-=(int t)
{
_day -= t;
while ( _day<1 )
{
_month--;
if (_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay(*this);
}
return *this;
}
-重载
与之前一样,我们复用-=的函数。
Date Date::operator-(int t)
{
Date tmp = *this;
tmp -= t;
return tmp;
}
我们如果对+负数,-负数会怎么样?显然程序会崩溃但这又可能是我们使用者的操作,于是便可以在不同的重载函数互相调用。
如下代码
Date Date::operator-(int t)
{
if (t < 0)
return *this + (-t);
Date tmp = *this;
tmp -= t;
return tmp;
}
日期相减
方法一
我们当然会求两个日期之间的差数。便可以重载-。我们可以对小的天数一直加一直到二者相等。如下
int Date::operator-(Date& d)
{
assert(*this >= d);
Date t1 = *this;
Date t2 = d;
int t = 0;
//一直加
while (t1 != t2)
{
t2 += 1;
t++;
}
return t;
}
我们有时也会写的前一个日期小,导致相差负数,为了达到这种效果也可以对上述代码稍加修改。
int Date::operator-(Date& d)
{
if (*this < d)
return d - *this;
Date t1 = *this;
Date t2 = d;
int t = 0;
//一直加
while (t1 != t2)
{
t2 += 1;
t++;
}
return t;
}
方法二
上述的代码十分简单,效率有些不理想,我们可以换种方法。
首先我们先判断二者是否相同,不相同在判断t2的天数是否小于t1,小于说明可能只是天数不同,将天数加到t1的天数,然后判断是否相等。如果t1的天数等于t2的天数,说明月份不同,将t2的月份一次往上加一判断二者是否相等。
int Date::operator-(Date& d)
{
if (*this < d)
return d - *this;
Date t1 = *this;
Date t2 = d;
int t = 0;
//一直加
while (t1 != t2)
{
int c = GetMonthDay(t2);
if (t2._day < t1._day && t1._day <= c)
{
t += t1._day - t2._day;
t2._day = t1._day;
}
else
{
t2._month++;
if (t2._month == 13)
{
t2._year++;
t2._month = 1;
}
t += c - t2._day + 1;
t2._day = 1;
}
}
return t;
}
这种方法的效率比第一种高了许多。
结语
到这里本篇文章就结束了。喜欢的点点关注!
全部代码如下。
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
public:
Date(int year=1900, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)
{
if (&d != this)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
//友元函数声明
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
//比较函数
bool operator==(Date& d);
bool operator>(Date& d);
bool operator<(Date& d);
bool operator<=(Date& d);
bool operator>=(Date& d);
bool operator!=(Date& d);
//加减操作函数
Date& operator+=(int t);
Date operator+(int t);
Date& operator-=(int t);
Date operator-(int t);
int operator-(Date& d);
int GetMonthDay(Date& d);
private:
int _year;
int _month;
int _day;
};
//bool Date::operator>(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<(Date& d)
//{
// return !(*this >= d);
//}
//bool Date::operator<=(Date& d)
//{
// return *this < d || d == *this;
//}
//bool Date::operator>=(Date& d)
//{
// return *this > d || d == *this;
//}
//bool Date::operator!=(Date & d)
//{
// return !(d == *this);
//}
bool Date::operator>(Date& d)
{
return _day + _month * 100 + _year * 10000 >
d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator<(Date& d)
{
return _day + _month * 100 + _year * 10000 <
d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator<=(Date& d)
{
return _day + _month * 100 + _year * 10000 <=
d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator>=(Date& d)
{
return _day + _month * 100 + _year * 10000 >=
d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator!=(Date& d)
{
return _day + _month * 100 + _year * 10000 !=
d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator==(Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
int Date::GetMonthDay(Date& d)
{
static int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (d._month == 2 && ((d._year % 400 == 0) || (d._year % 4 == 0 && d._year % 100 != 0)))
{
return 29;
}
return arr[d._month];
}
Date& Date::operator+=(int t)
{
if (t < 0)
return *this -= (-t);
_day += t;
while (_day > GetMonthDay(*this))
{
_day -= GetMonthDay(*this);
_month++;
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
Date Date::operator+(int t)
{
Date t1 = *this;
t1 += t;
return t1;
}
Date& Date::operator-=(int t)
{
if (t < 0)
return *this += (-t);
_day -= t;
while ( _day<1 )
{
_month--;
if (_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay(*this);
}
return *this;
}
Date Date::operator-(int t)
{
if (t < 0)
return *this + (-t);
Date tmp = *this;
tmp -= t;
return tmp;
}
int Date::operator-(Date& d)
{
if (*this < d)
return d - *this;
Date t1 = *this;
Date t2 = d;
int t = 0;
//一直加
while (t1 != t2)
{
int c = GetMonthDay(t2);
if (t2._day < t1._day && t1._day <= c)
{
t += t1._day - t2._day;
t2._day = t1._day;
}
else
{
t2._month++;
if (t2._month == 13)
{
t2._year++;
t2._month = 1;
}
t += c - t2._day + 1;
t2._day = 1;
}
}
return t;
}
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;
return in;
}