目录
一、类的创建和方法声明
二 、输出&运算符重载
三、检查合法性
1、获取对应年月的天数
2、初始化
四、实现加等和加操作
1、先写+=再写+
2、先写+再写+=
3、两种方式对比
五、实现++自增和--自减
1、自增
2、自减
六、 实现减等和减操作
1、减等天数
2、加负数减正数
3、减天数
4、日期减日期
完整版:
Date.h
Date.cpp
Test.cpp
通过之前的学习,这次我们用日期类对学习过的内容进行练习,先来复习一下。
默认成员函数如果我们不写,编译器会自动生成,我们写了,编译器就不会自动生成。
默认生成构造和析构:
- 内置类型不做处理
- 自定义类型会调用对应构造/析构
默认生成拷贝构造和赋值重载
- 内置类型完成浅拷贝/值拷贝(按字节一个一个拷贝)。
- 自定义类型,去调用这个成员拷贝构造/赋值重载
一、类的创建和方法声明
我们在头文件中创建日期类的成员变量(属性)和成员函数(方法)的声明,定义放在源文件中。
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
class Date {
public:
Date(int year = 0, int month = 0, int day = 0);
int GetMonthDay(int year, int month);
void Print();
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-=(int day);
// d1 - 100
Date operator-(int day);
// d1 - d2;
int operator-(const Date& d);
// ++d1
Date& operator++();
// d1++
Date operator++(int);
Date& operator--();
Date operator--(int);
private:
int _year;
int _month;
int _day;
};
二 、输出&运算符重载
详细讲解请看:运算符重载
void Date::Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
// d1 < d2
bool Date::operator<(const Date& d)
{
return _year < d._year
|| (_year == d._year && _month < d._month)
|| (_year == d._year && _month == d._month && _day < d._day);
}
// d1 <= d2
bool Date::operator<=(const Date& d)
{
return *this < d || *this == d;
}
// d1 > d2
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);
}
三、检查合法性
首先要检查日期合法性,月在1-12范围内,我们通过GetMonthDay获取对应年月的天数,在用于初始化的构造函数中检查日期。
1、获取对应年月的天数
int Date::GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
int _month[12] = { 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 _month[month - 1];
}
}
- 首先通过assert判断参数月份是否合法,不合法会报错。
- 接着创建存储对应月份天数的整型数组_month。
- 如果传入月份为二月,同时年份为闰年,那返回29天,否则返回_month数组中对应月份的天数。
2、初始化
Date::Date(int year, int month, int day)
{
if (month > 0 && month < 13
&& (day > 0 && day <= GetMonthDay(year, month)))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "日期非法" << endl;
}
}
- 首先检查如果月份合法且天数合法,则进行初始化。
- 否则输出“日期非法”提示初始化错误。
四、实现加等和加操作
1、先写+=再写+
日期加天数怎么算?
Date d1(2023, 2, 4);
Date d2 = d1 + 100;
思路:
- 当前日期天数相加,大于当前月份天数,月份进位,当月份大于12月,年份进位,返回加完结果*this,但是这种方式会改变d1,所以这种方式实际上实现的是+=。
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;
}
+=也需要返回值,+=支持连续+=,但日期类不支持加等类型不同,在下面的情况就+=必须要有返回值。
Date d3 = d1;
d1 = d3 += 100;
d1=d3+=100这种对日期类的操作可以支持,这时就需要加等操作的返回值了,从运算符的特性来说需要返回值。
我们通过复用加等操作实现加操作。
Date Date::operator+(int day)
{
Date tmp(*this);
tmp += day;
return tmp;
}
- 创建日期类对象tmp临时保存*this,
- 对tmp进行加等操作,
- 最后返回tmp(使用临时变量可以保证原变量不被改变,这符合+操作的定义)
另外,tmp是临时对象,出了作用域就不在了,所以不能使用传引用返回。
2、先写+再写+=
第二种方式可以先实现+操作,通过复用+操作实现+=操作。
Date Date::operator+(int day)
{
Date tmp(*this);
tmp._day += day;
while (tmp._day > GetMonthDay(_year, _month))
{
tmp._day -= GetMonthDay(_year, _month);
tmp._month++;
if (tmp._month == 13)
{
tmp._year++;
tmp._month = 1;
}
}
return tmp;
}
Date& Date::operator+=(int day)
{
*this = *this + day;
return *this;
}
- 在+操作中借助临时变量tmp存储为变化的*this的值,对tmp的成员day进行+操作,最后返回tmp。
- +=操作中使用*this对调用函数的对象进行+操作,最后返回*this。
如果使用static修饰 + 操作中的tmp使其成为静态变量,变为全局生命周期,是否能使用引用返回呢?
//Date.cpp
Date& Date::operator+(int day)
{
static Date tmp(*this);
tmp._day += day;
while (tmp._day > GetMonthDay(_year, _month))
{
tmp._day -= GetMonthDay(_year, _month);
tmp._month++;
if (tmp._month == 13)
{
tmp._year++;
tmp._month = 1;
}
}
return tmp;
}
//test.cpp
void TestDate2()
{
Date d1(2023, 2, 4);
d1.Print();
Date d2 = d1 + 100;
d2.Print();
Date d3 = d1 + 100;
d3.Print();
}
int main()
{
TestDate2();
return 0;
}
我们可以看到d2的结果是对的,但d3的结果不对,d3应该在d1的基础上加100,但实际输出结果是在d2的基础上加100.
接下来我们来调试看一下:
当运行到 Date d3 = d1 + 100; 时,在+操作内部对tmp初始化之前,为什么tmp的成员值和*this的成员值不一样呢?
原因:静态变量只能初始化一次,第一次Date d2 = d1 + 100;使用时进行了初始化,第二次再初始化就没有效果了,所以静态变量要慎用,不能为了传引用使用static修饰tmp。
3、两种方式对比
第一种是先写加等操作,复用加等实现加操作。
第二种是先写加操作,复用加实现加等操作。
那么上述两种方式哪个好呢?
结论:第一种比较好,第二种+操作存在消耗,初始化tmp需要拷贝构造一次,传值返回还需要一次拷贝构造;而加等没有拷贝行为
如果加等复用加会增加两次拷贝构造的使用次数,先写加、加等复用加一共消耗四次拷贝次数。先写加等函数不会有拷贝构造,只有加复用加等会使用两次拷贝构造。
这样一对比就可以得出第一种先写加等再写加比较好,减少拷贝次数。
减构成运算符重载和函数重载
五、实现++自增和--自减
1、自增
自增操作符++分为前置++和后置++。
默认只有前置++,同时++只有一个操作数,那就是隐藏的this指针,返回*this的值,可以使用引用返回。
//++d
Date& Date::operator++()
{
*this += 1;
return *this;
}
如何区分前置和后置呢?
编译器做了特殊处理,方便识别给后置加了一个参数int,没有实际意义,只是为了构成函数重载进行区分,后置需要返回++之前的值,所以使用临时变量tmp保存*this,对*this++,然后返回tmp保存的未改变的*this。
//d++
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
平时中能用前置就用前置,可以减少拷贝操作。
使用后置传个参数,任意一个即可,这个参数仅仅是为了占位,跟前置重载区分。
我们来测试一下:
void TestDate3()
{
Date d1(2023, 6, 6);
d1.Print();
Date ret1 = ++d1;
d1.Print();
ret1.Print();
Date ret2 = d1++;
d1.Print();
ret2.Print();
}
int main()
{
TestDate3();
return 0;
}
输出结果:
2、自减
同理,自减也与自增实现思路相同。
//--d
Date& Date::operator--()
{
*this -= 1;
return *this;
}
// d--
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
六、 实现减等和减操作
1、减等天数
在日期上做减法,如果减完小于等于零,则向上个月借日期,如果1月借日期则向上一年的12月借日期。
Date& Date::operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
void TestDate4()
{
Date d1(2023, 2, 4);
d1.Print();
d1 -= 100;
d1.Print();
}
int main()
{
TestDate4();
return 0;
}
输出结果:
2、加负数减正数
如果是下面这种情况呢?
void TestDate4()
{
Date d2(2023, 6, 6);
d2 += -100;
Date d3(2023, 7, 7);
d3 -= -200;
}
对加等和减等添加判断语句,如果加等负数则调用减等,减等整数负数则调用加等。
Date& Date::operator+=(int day)
{
if (day < 0)
{
*this -= -day;
return *this;
}
_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)
{
if (day < 0)
{
*this += -day;
return *this;
}
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
我们再来测试一下
void TestDate4()
{
Date d2(2023, 6, 6);
d2.Print();
d2 += -100;
d2.Print();
Date d3(2023, 7, 7);
d3.Print();
d3 -= -200;
d3.Print();
}
int main()
{
TestDate4();
return 0;
}
输出结果:
3、减天数
通过复用减等操作,同时借助临时变量tmp实现。
Date Date::operator-(int day)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
4、日期减日期
该函数通过递增较小的日期对象,直到两个日期对象相等,计算出天数差。如果当前日期对象大于传入的日期对象,则结果为正数;如果当前日期对象小于传入的日期对象,则结果为负数。
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;
}
- 首先,创建两个临时的日期对象 max 和 min,分别初始化为当前日期对象和传入的日期对象 d。
- 如果当前日期对象小于传入的日期对象 d,则交换 max 和 min,并将标志 flag 设置为 -1,表示结果为负数。
- 创建一个整型变量 n,用于记录天数差的绝对值。
- 使用循环,当 min 不等于 max 时,执行以下操作:递增 min 的日期,即执行 min.operator++()。同时递增 n。
- 返回 n 乘以 flag,得到最终的天数差。
测试一下
void TestDate5()
{
Date d1(2024, 1, 12);
d1.Print();
Date d2(2023, 11, 23);
d2.Print();
cout << d2 - d1 << endl;
cout << d1 - d2 << endl;
}
int main()
{
TestDate5();
return 0;
}
成功实现 :
完整版:
Date.h
#include <iostream>
#include <assert.h>
using namespace std;
class Date {
public:
Date(int year = 0, int month = 0, int day = 0);
void Print();
int GetMonthDay(int year, int 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-=(int day);
// d1 - 100
Date operator-(int day);
// d1 - d2;
int operator-(const Date& d);
// ++d1
Date& operator++();
// d1++
Date operator++(int);
Date& operator--();
Date operator--(int);
private:
int _year;
int _month;
int _day;
};
Date.cpp
#include "Date.h"
int Date::GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
int _month[12] = { 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 _month[month - 1];
}
}
Date::Date(int year, int month, int day)
{
if (month > 0 && month < 13
&& (day > 0 && day <= GetMonthDay(year, month)))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "日期非法" << endl;
}
}
Date& Date::operator+=(int day)
{
if (day < 0)
{
*this -= -day;
return *this;
}
_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)
{
Date tmp(*this);
tmp += day;
return tmp;
}
//Date Date::operator+(int day)
//{
// Date tmp(*this);
// tmp._day += day;
// while (tmp._day > GetMonthDay(_year, _month))
// {
// tmp._day -= GetMonthDay(_year, _month);
// tmp._month++;
// if (tmp._month == 13)
// {
// tmp._year++;
// tmp._month = 1;
// }
// }
// return tmp;
//}
//Date& Date::operator+=(int day)
//{
// *this = *this + day;
// return *this;
//}
//++d
Date& Date::operator++()
{
*this += 1;
return *this;
}
//d++
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
//--d
Date& Date::operator--()
{
*this -= 1;
return *this;
}
// d--
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
*this += -day;
return *this;
}
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator-(int day)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
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;
}
void Date::Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
// d1 < d2
bool Date::operator<(const Date& d)
{
return _year < d._year
|| (_year == d._year && _month < d._month)
|| (_year == d._year && _month == d._month && _day < d._day);
}
// d1 <= d2
bool Date::operator<=(const Date& d)
{
return *this < d || *this == d;
}
// d1 > d2
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);
}
Test.cpp
#include "Date.h"
void TestDate1()
{
Date d1(2023, 2, 4);
cout << d1.GetMonthDay(2022,12) << endl;
//d1.Print();
//Date d2 = d1 + 100;
//d2.Print();
}
void TestDate2()
{
Date d1(2023, 2, 4);
d1.Print();
Date d2 = d1 + 100;
d2.Print();
Date d3 = d1 + 100;
d3.Print();
}
void TestDate3()
{
Date d1(2023, 6, 6);
d1.Print();
Date ret1 = ++d1; // d1.operator++();
d1.Print();
ret1.Print();
Date ret2 = d1++; // d1.operator++(0);
d1.Print();
ret2.Print();
/*d1.operator++();
d1.operator++(0);*/
}
void TestDate4()
{
//Date d1(2023, 2, 4);
//d1.Print();
//d1 -= 100;
//d1.Print();
Date d2(2023, 6, 6);
d2.Print();
d2 += -100;
d2.Print();
Date d3(2023, 7, 7);
d3.Print();
d3 -= -200;
d3.Print();
}
void TestDate5()
{
Date d1(2024, 1, 12);
d1.Print();
Date d2(2023, 11, 23);
d2.Print();
cout << d2 - d1 << endl;
cout << d1 - d2 << endl;
}
int main()
{
TestDate5();
return 0;
}