目录
一、本文目的:
二、具体内容:
三、代码实现:
1、 获得月天数--GetMonthDay
2、构造函数初始化--Date
3、拷贝构造-- Date
4、 运算符重载比较大小(< <= > >= == !=)
5、运算符重载实现+= +与-= -
6、赋值重载的实现
7、(前置与后置)--和++的实现
8、日期相减
(完整代码:)
四、运算符重载中的复用问题
以便于更好理解c++的类的设计
1、构造(普通构造和拷贝构造)函数和析构函数的实现
2、运算符重载实现比较大小和理解高内聚和低耦合的关系
3、运算符重载实现+ - 等运算
知识点关键:懂得this指针的本质,构造函数如何构造,参数问题,引用做返回值问题,static修饰和inline内联函数作用问题等等
static:因为这个函数需频繁调用,而用static只会在第一次调用此函数时初始化,再调用就不会初始化,提高了效率
inline:这个函数需频繁调用,不用inline的话会不断地压栈,导致栈开销变大,inline会在调用的地方展开,就没有栈开销了,故频繁调用的函数可以用inline修饰
//获得这一年这个月有多少天
inline int GetMonthDay(int year, int month)
{//加一个static就只会在第一次调用GetMonthDay时初始化一次
static int monthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && (year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
monthDays[2] = 29;//如果是2月且是闰年,则为29天
return monthDays[month];
}
Date(int year = 0, int month = 1, int day = 1)
{
//1、首先要检查你传入的范围是否有效
if (year >= 0
&& month >= 1 && month <= 12
&& day >= 1 && day <= GetMonthDay(year,month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "非法日期" << endl;
}
}
参数用&:防止重复的拷贝构造(基础知识)
const:防止修改d
//拷贝构造函数
//Date d2(d1)
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
为什么要用运算符重载?
因为类是自定义类型,无法像内置类型一样直接比较大小,故利用运算符重载可实现自定义类型比较大小
< :先判断日期<的情况 -》①、年<年 ②、年相等,月<月 ③、年和月相等,日<日
如果上述情况不满足,就是>=
== :年和月和日都相等才行
<= :直接复用<和==即可
> :直接复用<=即可
>= :直接复用<即可
!= :直接复用==即可
//1、运算符重载实现比较大小
//d1 < d2 -》 d1.operator<(&d1,d2);
inline bool operator<(const Date& d)
{//本质的this指针调用
//bool operator<(Date* this, const Date& d)
if (_year < d._year)
return true;
else if (_year == d._year && _month < d._month)
return true;
else if (_year == d._year && _month == d._month && _day < d._day)
return true;
return false;//上面的情况都不满足,肯定就是false
}
inline bool operator==(const Date& d)
{//需多次调用的函数就可用inline(内联函数)
return _year == d._year && _month == d._month && _day == d._day;
}
// d1 <= d2 -》 d1.operator<=(&d1, d2);
//operator<=就类似于函数名,d1.operator相当于d1访问它的成员函数
//&d1是为this指针传参
bool operator<=(const Date& d)
{
//本质为bool operator<=(Date* this, const Date& d)
return *this < d || *this == d;
//本质为d1 < d || d1 == d 而这里的比较因为是自定义类型
//的还需要调用上面实现的两个运算符重载函数,即复用上面的来实现
}
bool operator>(const Date& d)
{
return !(*this <= d);//这里复用了实现的operator<=函数
}
bool operator>=(const Date& d)
{
return !(*this < d);
}
bool operator!=(const Date& d)
{
return !(*this == d);//复用了operator==函数
}
①+与+=有什么区别?
+不改变本身,改变的是另一变量
+=改变本身
比如int a = 0, b= 0; a+=1 -> a = 1 b=a+1 -> a并不改变
②、
+=:要先判断传入的天数是否合规,若<0,*this-=day,但自定义类型-=int类型的,无法直接减,故要先实现 operator-=,不能直接用_day-=day,因为日期相减不仅仅只是月天数相减那么简单,还要考虑年和月
+ : 要先拷贝构造一个临时对象ret,让其拷贝*this,返回临时对象ret就不要用引用了
-=:若减完天数<0,说明要往前借月来补这个天数,一定是从前一个月开始往前借,不能从这个月开始。
- : 与+同理
//2、运算符重载实现+ -等运算
Date operator+(int day)
{
//不是+=,不能改变传入的本身,所以需要先拷贝
//1、直接实现
//拷贝构造函数的调用
//Date ret(*this);//因为是+不是+=,故用d1拷贝构造一个ret
//ret._day += day;
//while (ret._day > GetMonthDay(ret._year, ret._month))
//{
// ret._day -= GetMonthDay(ret._year, ret._month);
// ret._month++;
// if (ret._month == 13)
// {
// ret._year++;
// ret._month = 1;
// }
//}
//return ret;//ret是局部变量,所以返回值不能用引用
//2、复用实现(推荐)
Date ret(*this);
ret += day;//ret.operator+=(day)
return ret;
}
//d1+=10
Date& operator+=(int day)
{//因为是+=,需要改变传入的本身,所以需先拷贝
if (day < 0)
{//如果传入的天数是负数
return *this -= -day;
}
_day += day;
if (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;//返回的是本身,出了作用域还在,返回值可以用引用
}
//d1 - 10
Date operator-(int day)
{
//1、直接实现
/*Date ret(*this);
ret._day -= _day;
while (ret._day <= 0)
{
--ret._month;
if (ret._month == 0)
{
--ret._year;
ret._month = 12;
}
ret._day += GetMonthDay(ret._year, ret._month);
}
return ret;*/
//2、复用实现(推荐)
Date ret(*this);
ret -= day;
return ret;
}
//d1 -= 10
Date& operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)//不合法的day需继续处理
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
赋值存在连续赋值,故operator=应返回Date类型
//赋值重载的实现
//d3 = d1
Date& operator=(const Date& d)
{
if (this != &d)//如果是两个相同的对象,则无需赋值操作
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
--和++分前置和后置的,如何区分前置与后置?
利用函数重载(--与++同理)
operator++()实现后置++,返回加之后的值
operator++(int)实现前置++,返回加之前的值
//++d1 -》 d1.operator++(&d1)
Date& operator++()
{
*this += 1;
return *this;//返回加之后的值,出了作用域还在
}
//d1++ -》 d1.operator++(&d1, 10) 任意传一个值即可
Date operator++(int)//为了构成函数重载,加一个int,但这个int不会被使用
{//写int 或者 int 变量名都可以
Date tmp(*this);
*this += 1;
return tmp;//返回加之前的值
}
//--d1
Date operator--()
{
*this -= 1;
return *this;
}
//d1--
Date operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
日期相减,不用直接减,看日期小的那一个到日期大的那一个需要++多少次即可知道相差多少天数,日期类的相比用operator<相比即可,先假设其中前一个为大后一个为小,再比较,最终确定大的日期和小的日期 ,若与假设相反,说明日期相减会为负数,故用flag来判断是否为负数
//d1 - d2 (日期相减:求相差多少天数)
int operator-(const Date& d)
{//思路:日期小的那一个一直++,直到与日期大的那个相等
//期间总共加了多少次,即相差的天数,但要注意,如果是负数该怎么办
int flag = 1;
Date max = *this;//利用拷贝构造
Date min = d;
if (max < min)//operator<
{
max = d;//operator=
min = *this;
flag = -1;//如果传入的日期相减会是负数,则标志改为-1
}
int n = 0;
while (min != max)//operator!=
{
++n;//operator++(),推荐调用前置++,因为相比后置少了一句代码
++min;
}
return n * flag;
}
①、声明和定义分离
分为三个文件:
Date.h:类的实现
Date.c:类的声明
Test.c:检测代码逻辑
②、this指针需要加上const
错误分析:&d3是const Date*类型的,而this指针是Date*的,它想接收d3类型则必须为const,所以要用const 修饰this指针,使其为const Date* 类型的
解释:本质上因为const对于非const的和const的都可以接收,因为权限的缩小是允许的,但若要改变成员变量,就不要const Date* this了(const修饰*this),不然就无法修改成员变量了。并且还有一种情况,如果一个成员函数使用了const修饰this,其他复用了这个成员函数的都要加上const修饰this指针,不然就是权限的放大了。
结论:什么时候给成员函数加上const,只要成员函数中无需改变成员变量最好都加上const。只要成员函数中不直接或间接改变成员变量,建议都加const修饰this指针
完整代码如下(VS2022下运行):
Date.h:
#pragma once
#include
using namespace std;
class Date
{
public:
int GetMonthDay(int year, int month) const;
Date(int year = 0, int month = 1, int day = 1);
Date(const Date& d);
inline bool operator<(const Date& d) const;//要么把内联去掉,要么内联函数就不要声明和定义相分离
inline 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) const;
Date& operator+=(int day);
Date operator-(int day) const;
Date& operator-=(int day);
Date& operator=(const Date& d);
Date& operator++();
Date operator++(int);
Date operator--();
Date operator--(int);
int operator-(const Date& d) const;
void Print() const;
private:
int _year;
int _month;
int _day;
};
Date.c:
#include"Date.h"
//获得这一年这个月有多少天
int Date :: GetMonthDay(int year, int month) const
{//加一个static就只会在第一次调用GetMonthDay时初始化一次
static int monthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && (year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
monthDays[2] = 29;//如果是2月且是闰年,则为29天
return monthDays[month];
}
//构造函数进行初始化
Date::Date(int year, int month, int day)//缺省参数声明和定义中只能留一个
{
//1、首先要检查你传入的范围是否有效
if (year >= 0
&& month >= 1 && month <= 12
&& day >= 1 && day <= GetMonthDay(year, month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "非法日期" << endl;
}
}
//拷贝构造函数
//Date d2(d1)
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//1、运算符重载实现比较大小
//d1 < d2 -》 d1.operator<(&d1,d2);
bool Date::operator<(const Date& d) const
{//本质的this指针调用
//bool operator<(Date* this, const Date& d)
if (_year < d._year)
return true;
else if (_year == d._year && _month < d._month)
return true;
else if (_year == d._year && _month == d._month && _day < d._day)
return true;
return false;//上面的情况都不满足,肯定就是false
}
bool Date::operator==(const Date& d) const
{//需多次调用的函数就可用inline(内联函数)
return _year == d._year && _month == d._month && _day == d._day;
}
// d1 <= d2 -》 d1.operator<=(&d1, d2);
//operator<=就类似于函数名,d1.operator相当于d1访问它的成员函数
//&d1是为this指针传参
bool Date::operator<=(const Date& d) const
{
//本质为bool operator<=(Date* this, const Date& d)
return *this < d || *this == d;
//本质为d1 < d || d1 == d 而这里的比较因为是自定义类型
//的还需要调用上面实现的两个运算符重载函数,即复用上面的来实现
}
bool Date::operator>(const Date& d) const
{
return !(*this <= d);//这里复用了实现的operator<=函数
}
bool Date::operator>=(const Date& d) const
{
return !(*this < d);
}
bool Date::operator!=(const Date& d) const
{
return !(*this == d);//复用了operator==函数
}
//2、运算符重载实现+ -等运算
Date Date::operator+(int day) const
{
//不是+=,不能改变传入的本身,所以需要先拷贝
//1、直接实现
//拷贝构造函数的调用
//Date ret(*this);//因为是+不是+=,故用d1拷贝构造一个ret
//ret._day += day;
//while (ret._day > GetMonthDay(ret._year, ret._month))
//{
// ret._day -= GetMonthDay(ret._year, ret._month);
// ret._month++;
// if (ret._month == 13)
// {
// ret._year++;
// ret._month = 1;
// }
//}
//return ret;//ret是局部变量,所以返回值不能用引用
//2、复用实现(推荐)
Date ret(*this);
ret += day;//ret.operator+=(day)
return ret;
}
//d1+=10
Date& Date::operator+=(int day)//this不加const,因为d1(成变)会改变
{//因为是+=,需要改变传入的本身,所以需先拷贝
if (day < 0)
{//如果传入的天数是负数
return *this -= -day;
}
_day += day;
if (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;//返回的是本身,出了作用域还在,返回值可以用引用
}
//d1 - 10
Date Date::operator-(int day) const
{
//1、直接实现
/*Date ret(*this);
ret._day -= _day;
while (ret._day <= 0)
{
--ret._month;
if (ret._month == 0)
{
--ret._year;
ret._month = 12;
}
ret._day += GetMonthDay(ret._year, ret._month);
}
return ret;*/
//2、复用实现(推荐)
Date ret(*this);
ret -= day;
return ret;
}
//d1 -= 10
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)//不合法的day需继续处理
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
//赋值重载的实现
//d3 = d1
Date& Date::operator=(const Date& d)
{
if (this != &d)//如果是两个相同的对象,则无需赋值操作
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
//++d1 -》 d1.operator++(&d1)
Date& Date::operator++()//this不加const,因为调用了+=,d1(成变)被改变
{
*this += 1;
return *this;//返回加之后的值,出了作用域还在
}
//d1++ -》 d1.operator++(&d1, 10) 任意传一个值即可
Date Date::operator++(int)//为了构成函数重载,加一个int,但这个int不会被使用
{//写int 或者 int 变量名都可以
Date tmp(*this);
*this += 1;
return tmp;//返回加之前的值
}
//--d1
Date Date::operator--()
{
*this -= 1;
return *this;
}
//d1--
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
//d1 - d2 (日期相减:求相差多少天数)
int Date::operator-(const Date& d) const
{//思路:日期小的那一个一直++,直到与日期大的那个相等
//期间总共加了多少次,即相差的天数,但要注意,如果是负数该怎么办
int flag = 1;
Date max = *this;//利用拷贝构造
Date min = d;
if (max < min)//operator<
{
max = d;//operator=
min = *this;
flag = -1;//如果传入的日期相减会是负数,则标志改为-1
}
int n = 0;
while (min != max)//operator!=
{
++n;//operator++(),推荐调用前置++,因为相比后置少了一句代码
++min;
}
return n * flag;
}
//打印年月日
void Date::Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
Test.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
int main()
{
Date d1(2023, 7, 27);
d1.Print();
Date d2(2023, 7, 28);
d1.Print();
//运算符重载比较大小
cout << (d1 < d2) << endl;//因为<<优先级大于<,所以加()
cout << (d1 > d2) << endl;
cout << (d1 == d2) << endl;
cout << (d1 != d2) << endl;
cout << (d1 <= d2) << endl;
cout << (d1 >= d2) << endl;
//是否要重载一个运算符,要看这个运算符是否对这个类的对象有意义
//比如日期-日期、日期-天数等是有意义的
//利用运算符重载实现计算
Date d4 = d1 + 100;
d4.Print();
d1 + 10;
d4 = d2 = d1;//连续赋值
d2.Print();
d4.Print();
cout << "d2 - d1:" << d2 - d1 << endl;
system("pause");
return 0;
}
运行结果如下:
我们以后工作里面用的复用用的多吗?耦合度是否大?
复用是内聚高,软件工程提倡的是低耦合,高内聚。
并且利用复用当成员变量(成员属性)变多时,只需改变那个被复用的,其他复用被复用的成员函数时就无需修改了,效率高,如果不复用,那每个成员函数可能都需要改变。