日期类
对日期类进行一个完善,可以帮助我们理解六个默认成员函数,这里选择全部显式实现,包括构造函数,运算符重载,赋值重载等。对有之前的知识点的提示,以及注意事项,这里采用声明和定义分离的形式,下面代码放的全部是定义,除非必要说明。在最后会有完整的代码。包括头文件和源文件内的。
1. 构造函数和析构函数
//构造函数,初始化列表
Date::Date(int year = 2024, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
//析构函数
Date::~Date() {}
2. 拷贝构造和赋值运算符重载
//拷贝构造函数
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//赋值运算符重载
Date& Date::operator=(const Date& d)
{
//可能出现d=d的情况,即自己给自己赋值,可以不用执行代码
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
3. 运算符重载
3.1 日期的比较
比较包括大于,等于,小于,大于或等于,小于或等于,不等于六种。
不需要全部都实现,复用代码即可。我们先写一个大于,日期的比较按照年月日依次比较,年大日期就大,年等的情况,月大日期就大,年等月等的情况,天大日期就大。剩下的所有情况就是不大于。
//运算符重载 大于
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 _year == d._year &&
_month == d._month &&
_day == d._day;
}
剩下的四种比较全部复用这两个函数即可。
小于就是不大于或不等于。
小于等于就是不大于
大于等于就是不小于
不等于就是等于取反
这种方法适用于所有需要重载这六种运算符的类,只需要写一个大于和等于或小于和等于,剩下的都复用代码即可。
//小于
bool Date::operator<(const Date& d)const
{
return !(*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 !(*this == d);
}
3.2 日期加减天数
加包括 + 和 +=,减包括 - 和 -=。前者不会修改左操作数,后者会修改左操作数,比如
a=10;
a+5 //不会修改a
a+=5 //会修改a
减同理。
这里先实现 += ,我们需要判断闰年,因为闰年2月有29天,因此,这里封装一个函数,用来获得某年某月的最大天数。
//内联函数声明定义不能分离,在调用处直接展开,不用建立栈帧
inline int GetMonthDay(int year, int month) //获得某年某月的天数
{
int MonthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
{
++MonthDay[2];
}
return MonthDay[month];
}
由于这个函数会频繁调用,因此将这个函数声明为内联函数,由于声明定义不能分离,所有这段代码是在头文件内。
有了这个函数后,在实现+=就比较容易了,我们不需要判断是不是闰年,是不是二月了,全部交给这个函数就可以了。
思路:把天全部加上,判断是否超过该月最大天数。有则进行修改,在判断是否超过最大月份,有则进行修改。循环,直到天数小于该月的最大天数。
那么+=代码如下
//日期加等天数(会改变*this)
Date& Date::operator+=(int day)
{
//+=一个负数,相当于-=一个正数
if (day < 0)
{
*this -= -day;
}
else
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
}
return *this;
}
+的实现,可以复用+=的代码
如下
//日期加天数(不会改变*this)
Date Date::operator+(int day) const
{
//加不改变*this,需要创建局部变量
Date retDate = *this;//拷贝构造
retDate += day;
//出作用域局部变量销毁,不能传引用返回
return retDate;
}
因为+不能改变*this,所以先拷贝一个对象,修改这个对象的内容,在返回。
-= 和 - 的重载很类似,就不赘述了,直接放码
//日期减天数
Date Date::operator-(int day) const
{
Date retDate = *this;
retDate -= day;
return retDate;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
*this += -day;
}
else
{
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
}
return *this;
}
我们的代码是否正确呢,大家可以自行测试。建议天数跨大一些,以保证不会出现特殊情况。
除了这些,还有++和–操作,并且分为前置和后置。实现起来也很容易,直接复用上面的代码即可。
//前置++,先++后使用
Date& Date::operator++()
{
*this += 1;
return *this;
}
//后置++,先使用后++,为了构成函数重载,后置++需要多一个int类型的参数,不需要我们传参
//函数调用时,编译器自动传参。
Date Date::operator++(int)
{
//返回修改前的值
Date retDate(*this);
*this += 1;
return retDate;
}
//前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
//后置--
Date Date::operator--(int)
{
Date retDate(*this);
*this -= 1;
return retDate;
}
3.3 日期减日期
日期的相加没有意义,但是相减还是比较有意义的,比如计算一下我们从出生到现在生活了多久,和对象已经在一起多久等。
有两种方法:
法一:
法二:
先比较日期,在对小日期进行自增,直到与大日期相等。
这里选择法二
//日期减日期
int Date::operator-(const Date& d) const
{
//找出日期大的和日期小的
Date maxDate = *this > d ? *this : d;
Date minDate = *this < d ? *this : d;
int flag = 1;
int day = 0;
//第一个日期较小,结果是负数。
if (minDate == *this)
{
flag = -1;
}
while (minDate != maxDate)
{
++minDate;
++day;
}
return day * flag;
}
3.4 流插入和流提取
对于内置类型,可以使用<<和>>来打印或者从键盘输入,自定义类型也可以重载一个流插入和流提取。
// >>和<<需要重载成全局函数,因为成员函数的第一个参数默认是this,重载成成员函数会打破 cout<<d 的习惯
//流插入重载
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
//流提取重载
istream& operator>>(istream& in,Date& d)
{
cin >> d._year >> d._month >> d._day;
return in;
}
istream和ostream是标准库内的一种输入输出类型,代码内的cou和in就是cout和cin,只不过是别名,在函数内部可以自己定义输出格式,甚至检查错误等。返回值是为了支持连续的输入和输出。
注意:在类外想使用类内部的私有成员,需要在类内部声明为友元函数。
4. 取地址和const取地址重载
这两个函数较为简单,就不过的讲解了。
//const取地址重载
const Date* Date::operator&()const
{
return this;
}
//取地址重载
Date* Date::operator&()
{
return this;
}
5. 完整代码
Date.h
#pragma once
#include<iostream>
using namespace std;
class Date
{
//友元函数声明,友元函数可以在类外使用类内的私有成员
friend ostream& operator<<(ostream& out,const Date& d);
friend istream& operator>>(istream& out,Date& d);
public:
//内联函数声明定义不能分离,在调用处直接展开,不用建立栈帧
inline int GetMonthDay(int year, int month) //获得某年某月的天数
{
int MonthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
{
++MonthDay[2];
}
return MonthDay[month];
}
//构造函数特征:
// 1、程序员不显示实现的话,编译器会自动生成默认构造函数
// 2、函数名和类名相同,没有返回值(也不能写void,这是规定)
// 3、在类对象实例化的过程中编译器对会自动调用对应的构造函数
// 4、无参的构造函数,全缺省的构造函数,编译器自动生成的构造函数都可以称为默认构造函数,
// 并且默认构造函数只能存在一个,虽然编译时没有问题,但在调用时会有歧义
// 5、构造函数支持函数重载
// 6、对内置数据类型如int,char,double等,构造函数不做处理,
// 对自定义数据类型,编译器会调用对应的默认构造函数
Date(int year = 2024, int month = 1, int day = 1);
//析构函数特征:
// 1、函数名为类名但需要在类名前加 ~ 符号,没有参数,没有返回值(也不能写void,这是规定)
// 2、我们不显示实现的话,编译器会自动生成对应的析构函数
// 3、在类对象生命周期结束后,编译器自动调用对应的析构函数
// 4、析构函数不能重载,因为析构函数没有参数
// 5、一个类只允许有一个析构函数
~Date() {};
//拷贝构造函数特征:
// 1、拷贝构造函数是构造函数的重载函数,所以函数名和类名相同
// 2、有且只有一个参数,该参数必须是类对象的引用,如果不是引用,编译器会直接报错
// 3、若没有显示实现,编译器会自动实现一个默认的拷贝构造函数,对已有的类对象进行拷贝
// 但是是按对应的字节序列完成拷贝,这种拷贝方式称为浅拷贝。
// 4、编译器生成的默认拷贝构造函数可以完成浅拷贝
Date(const Date& d);
//日期的比较,末尾const是限制*this的,此时this的类型为const Date* const this
//不能修改this指针,也不能修改this指针指向的内容
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=(const Date& d2);
Date operator+(int day)const;
Date& operator+=(int day);
Date operator-(int day) const;
Date& operator-=(int day);
Date& operator++();
Date operator++(int);
Date& operator--();
Date operator--(int);
int operator-(const Date& d) const;
const Date* operator&()const;
Date* operator&();
private:
int _year;
int _month;
int _day;
};
Date.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
//构造函数,初始化列表
Date::Date(int year = 2024, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
//析构函数
Date::~Date() {}
//拷贝构造函数
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//运算符重载 大于
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 _year == d._year &&
_month == d._month &&
_day == d._day;
}
//小于
bool Date::operator<(const Date& d)const
{
return !(*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 !(*this == d);
}
//Date Date::operator+(int day) const
//{
// Date retDate = *this;
// retDate._day += day;
// while (retDate._day > GetMonthDay(retDate._year, retDate._month))
// {
// retDate._day -= GetMonthDay(retDate._year, retDate._month);
// retDate._month++;
// if (retDate._month == 13)
// {
// retDate._year++;
// retDate._month = 1;
// }
// }
//
// return retDate;
//}
//Date& Date::operator+=(int day)
//{
// *this = *this + day;//赋值
// return *this;
//}
//日期加天数(不会改变*this)
Date Date::operator+(int day) const
{
//加不改变*this,需要创建局部变量
Date retDate = *this;//拷贝构造
retDate += day;
//出作用域局部变量销毁,不能传引用返回
return retDate;
}
//日期加等天数(会改变*this)
Date& Date::operator+=(int day)
{
if (day < 0)
{
*this -= -day;
}
else
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
}
return *this;
}
//日期减天数
Date Date::operator-(int day) const
{
Date retDate = *this;
retDate -= day;
return retDate;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
*this += -day;
}
else
{
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
}
return *this;
}
//前置++,先++后使用
Date& Date::operator++()
{
*this += 1;
return *this;
}
//后置++,先使用后++,为了构成函数重载,后置++需要多一个int类型的参数,不需要我们传参
//函数调用时,编译器自动传参。
Date Date::operator++(int)
{
Date retDate(*this);
*this += 1;
return retDate;
}
//前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
//后置--
Date Date::operator--(int)
{
Date retDate(*this);
*this -= 1;
return retDate;
}
//日期减日期
int Date::operator-(const Date& d) const
{
//找出日期大的和日期小的
Date maxDate = *this > d ? *this : d;
Date minDate = *this < d ? *this : d;
int flag = 1;
int day = 0;
//小日期减大日期为负数
if (minDate == *this)
{
flag = -1;
}
while (minDate != maxDate)
{
++minDate;
++day;
}
return day * flag;
}
// >>和<<需要重载成全局函数,因为成员函数的第一个参数默认是this,重载成成员函数会打破 cout<<d 的习惯
//流插入重载
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
//流提取重载
istream& operator>>(istream& in,Date& d)
{
cin >> d._year >> d._month >> d._day;
return in;
}
//赋值运算符重载
Date& Date::operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
const Date* Date::operator&()const
{
return this;
}
Date* Date::operator&()
{
return this;
}