目录
前言
本文主要了解关于类的六个默认成员函数,以及介绍关于运算符重载相关知识,最后会模拟实现一个日期类,以便巩固前面所有关于类与对象的知识点;
一、类的六个默认成员函数
一个类中我们没有主动定义任何成员,这样的类我们称作空类,空类中看上去什么都没有,实际上有六个默认的成员函数,本文主要围绕这六个默认成员函数展开话题;
其中,按功能,我们将其分为三类,其中本文主要介绍前两类,第三类几乎不需要我们自己实现,编译器会帮助我们生成一个默认的;
二、构造函数
构造函数是一个类中较为特殊的成员函数,主要负责对我们实现的类进行我们想要的初始化工作,当然,其中包含许多细节,会在后面依次介绍。
1、构造函数的引入
假如我们按照上一章内容继续定义一个栈,代码如下;具体栈的实现(C语言版)可以查看此文栈的实现
class Stack
{
public:
// 成员函数
void Init(int capacity = 4)
{
_arr = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _arr)
{
perror("malloc申请空间失败");
return;
}
_capacity = capacity;
_top = 0;
}
// 具体实现此处省略
void Push(int elem)
{
// ....
}
void Pop()
{
// ...
}
bool empty()
{
// ...
}
void Destroy()
{
free(_arr);
_arr = nullptr;
_top = _capacity = 0;
}
// 成员变量
private:
int* _arr;
int _top;
int _capacity;
};
int main()
{
// 定义栈
Stack st1;
// 对其进行操作
st1.Init(4);
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
// 销毁栈 -- 防止内存泄漏
st1.Destroy();
return 0;
}
我们发现以C语言的思维使用这个栈的时候,我们每次使用以及初始化都需要调用特殊的初始化函数,而且每次销毁栈时,我们也要调用销毁的函数,不然会发生内存泄漏;因此我们C++的构造函数因此而生;
2、构造函数的使用及特性
构造函数特性如下(一共七条)
1、函数名与类名相同
2、无返回值
3、对象在实例化时自动调用对应的构造函数
4、构造函数可以重载
首先我们根据前四条粗略的为上述类写一个构造函数
class Stack
{
public:
// 构造函数
Stack()
{
// 我们希望设计成默认初始化容量为4
_arr = (int*)malloc(sizeof(int) * 4);
if (nullptr == _arr)
{
perror("malloc申请空间失败");
return;
}
_capacity = 4;
_top = 0;
}
// 重载的构造函数
Stack(int capacity)
{
_arr = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _arr)
{
perror("malloc申请空间失败");
return;
}
_capacity = capacity;
_top = 0;
}
// 省略具体实现
void Push(int elem)
{
// ....
}
// 成员变量
private:
int* _arr;
int _top;
int _capacity;
};
如上,我们设计了两个构造函数(利用了特性四),第一个构造函数为无参构造,我们将初始容量设置为4,第二个构造为有参构造,其参数为我们初始化容量的大小;我们在创建对应的对象的同时,调用其对应的构造函数初始化对象(特性3);可通过F11逐语句调试证实这点;
int main()
{
// 使用了第一个构造函数
Stack st1;
// 使用第二个构造函数
Stack st2(10);
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
return 0;
}
当我们创建一个对象时,我们会根据参数匹配对应的构造函数,有的小伙伴可能就好奇了,第一个构造后面为什么没有加圆括号呢?
int main()
{
Stack st1; // 1
Stack st2(); // 2
return 0;
}
可不可以像代码2一样初始化创建对象呢?
答案是否定的,当我们像代码2一样初始化对象时,编译器会将其识别成返回值为Stack类型,函数名为st2,无参数的函数声明了;
5、如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义编译器则不会生成;
class Date
{
public:
// 没有定义下列函数时,会生成默认构造函数
//Date(int year, int month, int day)
//{
// _year = year;
// _month = month;
// _day = day;
//}
//Date(const Date& d)
//{
// _year = d._year;
// _month = d._month;
// _day = d._day;
//}
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
这一点也很好理解,当类中没有显示定义任何构造函数时,编译器才能生成默认的构造函数,注意:没有显示定义的构造函数也包括后面所说的拷贝构造,但不包括赋值重载;
6、编译器生成的默认构造函数对内置类型不做处理,对自定义类型则调用其默认构造;(内置类型包含整型浮点型以及指针类型,而自定义类型主要包含struct、class定义的类与union定义的联合体 和 enum定义的枚举)
7、默认构造函数指的是这三个函数,分别是无参构造函数、全缺省构造函数、编译器帮我们自动生成的默认构造函数,除此以外,其他的构造函数都不能称作默认构造函数;
class Time
{
public:
Time()
{
_hour = 0;
_minute = 0;
_second = 0;
}
int _hour;
int _minute;
int _second;
};
class Date
{
public:
void Print()
{
printf("%04d/%02d/%02d %02d:%02d:%02d\n",
_year, _month, _day, _time._hour,
_time._minute, _time._second);
}
private:
int _year;
int _month;
int _day;
Time _time;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
运行上面代码,我们发现Date类中的内置类型(年月日)未做任何处理,为随机值,而对于自定义类型Time,调用了其构造函数,将时分秒都初始化成了0;
三、析构函数
1、析构函数的引入
回到之前的话题,构造函数的引入,我们发现我们已经解决了对象定义时自动初始化的问题,但是我们仍然还要注意在不用这个对象时,对其销毁,不然会引发内存泄漏的问题,那有没有什么办法能够将销毁也弄成自动化呢?这便是接下来我们介绍的析构函数,它能帮助我们将销毁对象处理成自动化;
2、析构函数的使用及特性
析构函数的特性与构造函数类似(一共有以下如下六条)
1、析构函数名是在类名前加上字符 ~
2、无参数无返回值(注意:析构无参数,而构造可能有参数)
3、一个类只能有一个析构函数,若未显式定义,则会自动生成一个默认的析构函数(注意:析构函数不能重载)
4、对象声明周期结束时,自动调用析构函数
class Stack
{
public:
// 构造函数
Stack()
{
// 我们希望设计成默认初始化容量为4
_arr = (int*)malloc(sizeof(int) * 4);
if (nullptr == _arr)
{
perror("malloc申请空间失败");
return;
}
_capacity = 4;
_top = 0;
}
Stack(int capacity)
{
_arr = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _arr)
{
perror("malloc申请空间失败");
return;
}
_capacity = capacity;
_top = 0;
}
void Push(int elem)
{
// ....
}
void Pop()
{
// ...
}
bool empty()
{
// ...
}
//void Destroy()
//{
// free(_arr);
// _arr = nullptr;
// _top = _capacity = 0;
//}
// 析构函数
~Stack()
{
free(_arr);
_arr = nullptr;
_top = _capacity = 0;
}
// 成员变量
private:
int* _arr;
int _top;
int _capacity;
};
根据以上特性,写出了如上构造函数,由于析构在对象销毁前会自动调用,故无须手动销毁,极大的简化了代码。
5、当我们不写析构函数时,编译器会帮助我们自动生成一个默认的析构函数,该析构函数对内置类型不做处理,对自定义类型则调用他们自己的析构函数,可用以下日期类证明。
class Time
{
public:
Time()
{
_hour = 0;
_minute = 0;
_second = 0;
}
~Time()
{
cout << "~Time()" << endl;
}
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date()
{
_year = 1970;
_month = 1;
_day = 1;
}
private:
int _year;
int _month;
int _day;
Time _time;
};
我们创建一个Date类对象,当这个对象出作用域销毁时,会调用编译器自动生成的Date类的析构函数,该析构函数,对年月日等不做处理,对Time类,调用time类的析构,打印出了~time;
四、拷贝构造
1、初步了解拷贝构造
在我们真实开发中,是否存在需要拷贝一个与其相同的对象呢?实际上,这种场景时普遍存在的,对于内置类型的拷贝,我们可以直接选择用等号初始化,那我们是否可以用一个自定义类型初始化一个自定义类型的对象呢?这就轮到我们的拷贝构造函数登场了;
2、拷贝构造的使用及特征
拷贝构造的特征:
1、拷贝构造是普通构造函数的一个重载形式 -> 具有构造函数的某些特征(与类名同名,无返回值)
2、拷贝构造函数的参数只有一个且必须为类类型对象的引用。
class Stack
{
public:
// 构造函数
Stack(int capacity = 4)
{
_arr = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _arr)
{
perror("malloc申请空间失败");
return;
}
_capacity = capacity;
_top = 0;
}
// 拷贝构造
Stack(const Stack& s)
{
_arr = (int*)malloc(sizeof(int) * s._capacity);
if (nullptr == _arr)
{
perror("malloc申请空间失败");
return;
}
memcpy(_arr, s._arr, sizeof(int) * s._capacity);
_top = s._top;
_capacity = s._capacity;
}
void Push(int elem)
{
// ....
}
~Stack()
{
free(_arr);
_arr = nullptr;
_top = _capacity = 0;
}
// 成员变量
private:
int* _arr;
int _top;
int _capacity;
};
int main()
{
Stack st1;
// 调用拷贝构造
Stack st2(st1);
// 这也是调用拷贝构造
Stack st3 = st1;
}
关于第二条特性,为什么必须是同类对象的引用呢?不能直接传值么?
我们都知道,形参是实参的一份临时拷贝,也就是说,我们要得到形参,我们也需要对实参进行拷贝得到,而拷贝则其实就是调用了拷贝构造,也就会去调用其构造函数,因此,此处会形成一个无限循环的问题,故编译器规定拷贝构造的函数参数必须为该类对象的引用;既然是拷贝构造,不会对被拷贝对象进行修改,因此,我们加上const缩小权限;
3、当我们没有显示定义拷贝构造函数时,编译器也会帮我们生成一个默认的拷贝构造函数,这个拷贝构造函数对内置类型进行值拷贝(浅拷贝),对于自定义类型则是调用他们自己的拷贝构造函数;
可能会有会有好多小伙伴就会想了,既然默认生成的拷贝构造也对内置类型做处理,那么是否不需要自己写构造函数了呢?确实,有了这个自动生成的拷贝构造函数,确实有许多类无需写拷贝构造函数,但对于有些类却行不通,这里就涉及深浅拷贝的相关知识了;
补充:深浅拷贝
什么是浅拷贝,什么又是深拷贝?
所谓浅拷贝,则是按字节拷贝,逐字节拷贝的方式,深拷贝则是对于栈上申请的空间按照逐字节拷贝,对于动态申请(堆上)的,将其堆上的资源先拷贝下来,放进某个变量中(常为指针),比如以下对于栈深浅拷贝与日期类的深浅拷贝的对比;
这种浅拷贝是否合理呢?当我们对一个对象增删查改时,也会影响另一个对象;很显然是不合理的一种方案;
很显然,这种深拷贝才满足我们的需求,当我们对一个对象进行操作时,无论如何操作也不会影响另一个对象;
那么什么样的类需要我们手动写拷贝构造函数,什么样的类可以使用编译器帮我们生成的呢?
可以根据该类是否需要手动实现析构函数来判断该类是否需要我们手动写拷贝构造,因为一旦需要写析构函数,那么说明这个类一定有我们在堆上申请的资源需要释放,一旦有堆上申请的资源,就涉及深浅拷贝问题,不过,最终实现由我们具体需求来进行判断是否需要自己实现;
五、赋值运算符的重载
想要了解赋值运算符的重载,我们先得了解运算符重载的知识;
1、运算符重载
为了增加代码的可读性,C++增加了运算符重载的概念,与函数重载不同,运算符重载是给运算符增加一些新功能;运算符重载是特殊的函数;他们也可以有参数、返回值等;
格式:
返回值 operator 被重载的运算符(形参)
{
函数体;
}
运算符重载具有如下特性:
1、被重载的运算符必须为已存在的运算符,不能自己创建新的运算符;
2、被重载的运算符其参数必须有一个为自定义类型参数
3、对于被重载的运算符,保持其原有的性质,比如 ‘+’,对于其两个操作数都为整型时,还是为两个整型相加的功能;
4、作为类成员被重载时(在类内),由于其默认会传this指针,因此实际传参时,传参的个数减1;
5、对于以下五个操作符,不能对其重载,分别为 .* :: sizeof ?: .
class Date
{
public:
Date(int year = 1970, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int GetMonthDays(int year, int month)
{
int days[13] = { 0 ,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) && month == 2)
{
return 29;
}
else
{
return days[month];
}
}
// 重载 + 号
Date operator+(int day)
{
Date tmp = *this;
tmp._day += day;
while (tmp._day > GetMonthDays(tmp._year, tmp._month))
{
tmp._day -= GetMonthDays(tmp._year, tmp._month);
tmp._month++;
if (tmp._month == 13)
{
tmp._year++;
tmp._month = 1;
}
}
return tmp;
}
void Print()
{
printf("%04d/%02d/%02d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 5, 31);
// 两种调用方式
Date d2 = d1 + 100;
Date d3 = d2.operator+(100);
return 0;
}
在实现上述日期加天数中,由于运算符重载被设置成类内成员,因此少穿了一个参数,在调用处,我们可以使用两种不同的方式调用;
2、赋值运算符重载
赋值运算符重载也为运算符重载的一种,是类内默认成员之一;
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
特征:
1、赋值运算符必须为类内成员,不能重载为全局函数(C++ primer 500 页)
2、用户没有显示定义时,会生成一个默认的赋值重载,该默认赋值重载对内置类型进行赋值,对自定义类型则调用他们的复制重载;
为什么不能定义成全局的呢?其实根据上面两点特性就可以推导出;
当我们定义为全局时,类内没有发现赋值重载,故编译器会生成一个默认的赋值重载,而类外也有一个赋值重载,此时调用处不知道调用哪一个,形成二义性;
3、前置++和后置++重载
在某些类中,我们也可以自定义++动作,也是运算符重载的一部分,这里用日期类模拟,首先我们将+=的逻辑写出来;
Date& operator+=(int day)
{
_day += day;
while (_day > GetMonthDays(_year, _month))
{
_day -= GetMonthDays(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
前置++;
Date& operator++()
{
*this += 1;
return *this;
}
后置++
Date operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
后置++这里,我们需要传一个int作为占位,构成重载,表示当前为后置++,其中形参不需要我们写形参名,传参数时也不需要传整型;
六、取地址与const取地址重载
取地址与const取地址也是六大默认成员函数之一,几乎不需要我们自己实现,我们使用编译器帮助我们默认生成的即可,下面会模拟实现一份编译器默认生成的,在此之前,我们首先了解const成员函数相关概念;
1、const成员函数
我们都知道,我们可以通过const来修饰变量(对象),在C++中,我们也可以通过const来修饰类的成员函数;那么修饰成员函数有什么作用呢?看以下代码;
class A
{
public:
A(int a = 10)
{
_a = a;
}
void Print()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A a1;
a1.Print(); // ????
const A a2;
a2.Print(); // ????
return 0;
}
以上两次打印部分的代码是否能够正常运行呢?
a1的打印部分的代码可以正常编译,a2的打印部分的代码会编译报错,因为当我们定义了一个const对象时,我们调用其打印函数,传入this指针,通常这个this指针的类型为 A* const this , 这里const仅仅修饰this指针的指向,不允许修改this指针的指向,却允许修改this指针指向的内容,这明显是不合乎情理的,我们在主函数定义了一个cosnt对象,传入print函数后却允许修改了,这是一种权限放大的行为。
此时,我们必须使用const修饰这个函数,使this指针称为 const A* const this这样的类型,具体使用如下;
void Print() const
{
cout << _a << endl;
}
注意,const函数的const仅修饰this指针,不修饰其他参数,通常建议在不修改类内成员的函数使用cosnt来修饰;
2、取地址与const取地址重载
理解了如上知识点,此处就很好解释了,每个类如果不显示的定义取地址重载和const取地址重载时,都会默认生成, 如下模拟默认生成的取地址;
class A
{
public:
A(int a = 10)
{
_a = a;
}
void Print()
{
cout << _a << endl;
}
// 取地址重载
A* operator&()
{
return this;
}
// const 取地址重载
const A* operator &() const
{
return this;
}
private:
int _a;
};
实际上这两个操作符的重载是 运算符重载 + 函数重载,一个针对const对象,一个针对普通对象;
七、模拟实现日期类
对前面知识的总结,毫无疑问,日期类的实现是最好的联系方法,接下来我将模拟实现一个日期类,来巩固上述所有关于类的知识;
我们依旧将按项目的方式(声明与定义分离),首先我们要考虑我们要实现一个类,最开始应该考虑哪些,我认为是成员变量与构造函数---(默认构造,拷贝构造,赋值重载)
// Date.h文件
class Date
{
public:
// 构造函数
Date(int year = 1970, int month = 1, int day = 1);
// 拷贝构造
Date(const Date& d);
// 赋值重载
Date& operator=(const Date& d);
private:
// 获取当前月天数
int GetMonthDays(int year, int month);
private:
int _year;
int _month;
int _day;
};
// Date.cpp文件
// 构造函数
Date::Date(int year, int month, int day)
{
if ((month >= 1 && month < 13) && ((day <= GetMonthDays(year, month) && day >= 1)))
{
_year = year;
_month = month;
_day = day;
}
else
{
std::cout << "日期不合法" << std::endl;
exit(-1);
}
}
// 拷贝构造
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// 运算符重载
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
// 获取当前月天数
int Date::GetMonthDays(int year, int month)
{
int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) && month == 2)
{
return 29;
}
else
{
return days[month];
}
}
这里的构造函数有个小细节,当我们声明与定义分离设计程序时,我们将缺省值放在声明处即可,无需放在定义处(也不能)。我们在用户给定日期时,需要判断日期是否合法,因此此处增加一个查找当前月的天数的函数;
// Date.h文件
// 运算符重载
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.cpp文件
// 运算符重载
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
{
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;
}
bool Date::operator>=(const Date& d) const
{
return (*this == d) || (*this > d);
}
bool Date::operator<(const Date& d) const
{
return !(*this >= d);
}
bool Date::operator<=(const Date& d) const
{
return !(*this > d);
}
以上为各种逻辑判断运算符的重载,这里也有几个需要注意的点;
首先,如果在声明处用const修饰了成员函数,在定义的部分也需要用const来修饰;
其次,在实现以上6个成员函数时,不必一一实现,我们可以同时实现其中的三个 (=,<, <=),然后用这三个进行复用实现另外三个,不一定时上述代码中的那三个,逻辑合理即可;
最后,我们在实现上面大于逻辑时,不能写成以下逻辑的代码;
bool Date::operator>(const Date& d) const
{
if (_year > d._year)
{
return true;
}
else if (_month > d._month)
{
return true;
}
else if (_day > d._day)
{
return true;
}
return false;
}
因为年不大于而月大于时,不一定整体大于,比如2022.03.21与2023.02.21,如果用这个例子跑上述代码明显会出现逻辑错误;
// Date.h文件
Date& operator+=(int day);
Date operator+(int day) const;
Date& operator-=(int day);
Date operator-(int day) const;
// Date.cpp文件
Date& Date::operator+=(int day)
{
if (day < 0)
{
*this -= -day;
return *this;
}
_day += day;
while (_day > GetMonthDays(_year, _month))
{
_day -= GetMonthDays(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
Date Date::operator+(int day) const
{
if (day < 0)
{
return *this - (-day);
}
// 复用 +=
Date tmp(*this);
tmp += day;
return tmp;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_year--;
_month = 12;
}
// 加上一个月的天数
_day += GetMonthDays(_year, _month);
}
return *this;
}
Date Date::operator-(int day) const
{
if (day < 0)
{
return *this + (-day);
}
// 复用-=
Date tmp(*this);
tmp -= day;
return tmp;
}
以上实现的时加减相关的运算符重载,在实现以上重载时,也有一些需要注意的点;
1、我们可以先实现+=或-=,然后复用其实现+和-,有些小伙伴就有疑问了,那可不可以先实现+和-,然后用其复用实现+=和-=呢?当然是可以的,但是,如果后者我们平均一次+=就要拷贝构造两次,而前者则一次都不需要,消耗略大,故选择前者;
2、在实现+和-时,用const修饰,防止传入的对象为const,引发权限放大的编译错误;
3、注意可能会出现传入的day为负数的情况,此时我们要进行特殊处理;
// Date.h文件
Date& operator++();
Date operator++(int);
Date& operator--();
Date operator--(int);
// Date.cpp文件
// 前置++
Date& Date::operator++()
{
*this += 1;
return *this;
}
// 后置++
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
// 前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
// 后置--
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
以上的前置与后置++/--都是复用+=与-=,我们用参数列表中的int区分前置与后置的区别;
// Date.h文件
// 在类内声明成友元
friend std::ostream& operator << (std::ostream& out, const Date& d);
friend std::istream& operator >> (std::istream& in, const Date& d);
// 在全局作用域里声明
std::ostream& operator << (std::ostream& out, const Date& d);
std::istream& operator >> (std::istream & in, const Date & d);
// Date.cpp文件
std::ostream& operator << (std::ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日";
return out;
}
std::istream& operator >> (std::istream& in, const Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
以上为流插入与流提取的重载,重载完这两个运算操作符后,我们可以对我们的日期类进行个性化的输出与输出了,其中的需要注意的是,我们对我们的这两个运算符重载进行了两次声明,一次是在全局作用域声明,一次在类内部将其声明为该类的友元函数,友元的作用是让该类可以访问类内部的私有成员,具体的介绍在类与对象(下)将会进行详细的介绍;
// Date.h文件
// 日期减日期
int operator-(const Date& d) const;
// Date.cpp文件
// 日期减日期
int Date::operator-(const Date& d) const
{
Date max = *this;
Date min = d;
int flag = 1;
if (max < min)
{
max = d;
min = *this;
flag *= -1;
}
int day = 0;
while (min < max)
{
++min;
++day;
}
return day * flag;
}
以上为日期减日期的实现,我们可以实现日期减日期,但是不能用日期加日期,结果不合理;
最后附上所有代码总结:
// Date.h文件
#pragma once
#include <iostream>
class Date
{
public:
friend std::ostream& operator << (std::ostream& out, const Date& d);
friend std::istream& operator >> (std::istream& in, Date& d);
// 构造函数
Date(int year = 1970, int month = 1, int day = 1);
// 拷贝构造
Date(const Date& d);
// 运算符重载
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+=(int day);
Date operator+(int day) const;
Date& operator-=(int day);
Date operator-(int day) const;
Date& operator++();
Date operator++(int);
Date& operator--();
Date operator--(int);
// 日期减日期
int operator-(const Date& d) const;
// 赋值重载
Date& operator=(const Date& d);
private:
// 获取当前月天数
int GetMonthDays(int year, int month);
private:
int _year;
int _month;
int _day;
};
std::ostream& operator << (std::ostream& out, const Date& d);
std::istream& operator >> (std::istream & in, Date & d);
// Date.cpp文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "Date.h"
// 构造函数
explicit Date::Date(int year, int month, int day)
{
if ((month >= 1 && month < 13) && ((day <= GetMonthDays(year, month) && day >= 1)))
{
_year = year;
_month = month;
_day = day;
}
else
{
std::cout << "日期不合法" << std::endl;
exit(-1);
}
}
// 拷贝构造
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// 运算符重载
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
{
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;
}
bool Date::operator>=(const Date& d) const
{
return (*this == d) || (*this > d);
}
bool Date::operator<(const Date& d) const
{
return !(*this >= d);
}
bool Date::operator<=(const Date& d) const
{
return !(*this > d);
}
Date& Date::operator+=(int day)
{
if (day < 0)
{
*this -= -day;
return *this;
}
_day += day;
while (_day > GetMonthDays(_year, _month))
{
_day -= GetMonthDays(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
Date Date::operator+(int day) const
{
if (day < 0)
{
return *this - (-day);
}
// 复用 +=
Date tmp(*this);
tmp += day;
return tmp;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_year--;
_month = 12;
}
// 加上一个月的天数
_day += GetMonthDays(_year, _month);
}
return *this;
}
Date Date::operator-(int day) const
{
if (day < 0)
{
return *this + (-day);
}
// 复用-=
Date tmp(*this);
tmp -= day;
return tmp;
}
// 前置++
Date& Date::operator++()
{
*this += 1;
return *this;
}
// 后置++
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
// 前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
// 后置--
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
std::ostream& operator << (std::ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日";
return out;
}
std::istream& operator >> (std::istream& in, Date& d)
{
in >> 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;
}
// 日期减日期
int Date::operator-(const Date& d) const
{
Date max = *this;
Date min = d;
int flag = 1;
if (max < min)
{
max = d;
min = *this;
flag *= -1;
}
int day = 0;
while (min < max)
{
++min;
++day;
}
return day * flag;
}
// 获取当前月天数
int Date::GetMonthDays(int year, int month)
{
int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) && month == 2)
{
return 29;
}
else
{
return days[month];
}
}