📚C++初阶
-【 C++发展史、命名空间、输入输出、缺省参数、函数重载 】
-【 C++引用、内联函数、auto、范围 for、nullptr 】


🚀 个人主页:< 脏脏a-CSDN博客 >
📊 文章专栏:< C++ >
🔗 上篇回顾:<【中篇】类和对象 >
📋 其他专栏:< Linux > 、< 数据结构 > 、< 优选算法 >

目录
一、const成员
二、日期类的默认成员函数
三、获得某年某月的天数
四、日期加天数
【思考】:先实现operator+=,还是先实现operator+
五、日期减天数
六、前置++、--,后置++、--
七、运算符重载
八、日期减日期
九、流插入和流提取运算符重载
9.1 流插入运算符 operator<< 重载
9.2 流提取运算符 operator>> 重载
将 const 修饰的 “ 成员函数 ” 称之为 const 成员函数 , const 修饰类成员函数,实际修饰该成员函数
隐含的 this 指针 ,表明在该成员函数中 不能对类的任何成员进行修改。

由于this指针原本的类型是类名* const this,当成员函数被const修饰时 ,this的类型会从类名* const this,变为const 类名* const this,也就是说this指针和this指针指向的对象都不能被修改了。
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << "Print()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
void Print() const
{
cout << "Print()const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
void Test()
{
Date d1(2022,1,13);
d1.Print();
const Date d2(2022,1,13);
d2.Print();
}根据上述代码,我们来解决4个问题:
class Date
{
public:
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1)
{
if (month > 0 && month < 13 && day > 0 && day <= GetMonthDay(year, month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout<<"非法构造"<<endl;
assert(0);
}
}
// 拷贝构造函数
// d2(d1)
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day ;
}
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
// 析构函数
~Date()
{
_year = 0;
_month = 0;
_day = 30;
}
private:
int _year;
int _month;
int _day;
};日期类的默认成员函数完全可以只写一个构造函数,因为日期类的成员变量都是内置类型,对于拷贝构造和赋值重载编译器默认生成的浅拷贝完全够用了,至于析构函数,日期类没有申请动态资源,内置类型出了作用域,编译器会自动销毁,不会涉及资源释放等问题
关于计算日期,我们最常用的就是获得某年某月天数的接口,由于日期类的成员变量是私有的,所以只能在类内部实现这个接口(声明:本章节的所有接口我都没进行声明和定义分离,你们自己私下实现的时候可以自己进行声明和定义分离)
// 获取某年某月的天数
int GetMonthDay(int year, int month)
{
static int days[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)
{
return 29;
}
return days[month];
} // 日期+=天数
Date& operator+=(int day)
{
_day = _day + day;
while (_day > GetMonthDay(_year,_month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_year +=1;
_month = 1;
}
}
return *this;
}
// 日期+天数
Date operator+(int day)
{
Date tmp(*this);
tmp+=day;
return tmp;
}_day先加上day,通过减去当前月的天数并增加月份来进行进位,循环此过程,直到_day的数值处于当前月的天数范围内时,停止调整,完成日期的进位计算。
从图中可以看到,先+后+=比先+=后+多了一次赋值运算符重载,所以,先+=后+效率更高
这个接口的效率分析和日期加天数是没区别的,所以依旧先实现-=,在复用-=
// 日期-天数
Date operator-(int day)
{
Date tmp(*this);
tmp-=day;
return tmp;
}
// 日期-=天数
Date& operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay(_year,_month);
}
return *this;
}先给_day减去day,只要_day在当前月天数的范围内,就不用再进行操作了,反之,只要_day<=0,就代表不符合范围,就要进行借月份加天数操作,依次循环进行操作,直到_day符合当月天数,如果_month==0了,就需要进行借年操作,同时更新年和月份
// 前置++
Date& operator++()
{
*this+=1;
return *this;
}
// 后置++
Date operator++(int)
{
Date tmp(*this);
*this+=1;
return tmp;
}
// 后置--
Date operator--(int)
{
Date tmp(*this);
*this-=1;
return tmp;
}
// 前置--
Date& operator--()
{
*this-=1;
return *this;
}直接复用日期加减天数操作,注意一点,不能返回局部变量的引用
// >运算符重载
bool 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;
}
a
// ==运算符重载(const修饰,简化返回)
bool operator==(const Date& d) const {
return _year == d._year && _month == d._month && _day == d._day;
}
// >=运算符重载(const修饰)
bool operator >= (const Date& d) const {
return *this > d || *this == d;
}
// <运算符重载(const修饰)
bool operator < (const Date& d) const {
return !(*this >= d);
}
// <=运算符重载(const修饰)
bool operator <= (const Date& d) const {
return !(*this > d);
}
// !=运算符重载(const修饰)
bool operator != (const Date& d) const {
return !(*this == d);
}只要实现了operator > 和 operator == ,别的直接复用即可
思路1:暴力求解,小的日期一直++,如果和大的日期相等了,就能计算相差天数
int Date::operator-(const Date& d) const
{
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;
}缺点:虽然实现简单,但是效率低
时间复杂度:两个日期的相差天数,也就是O(N)
思路2:先确定两个日期的大小,让大、小日期分别减到当年1月1日,这个时候就能确定当前年差了几天,然后再计算差了几年,如果是闰年就+366,平年就+365
int Date::operator-(const Date& d) const {
Date max = *this;
Date min = d;
int flag = 1;
// 区分大小日期,修正flag
if (*this < d) {
max = d;
min = *this;
flag = -1;
}
int count = 0;
// max减到当年1月1日
while (!(max._day == 1 && max._month == 1)) {
--max; // 依赖operator--(日期减一天)
++count;
}
// min减到当年1月1日
while (!(min._day == 1 && min._month == 1)) {
--min; // 依赖operator--(日期减一天)
--count;
}
// 计算年份差的总天数
while (min._year != max._year) {
if (is_leapyear(min._year)) {
count += 366;
} else {
count += 365;
}
min._year++; // 这里假设可以直接修改成员变量,若封装严格需用接口
}
return flag * count;
}时间复杂度:算当前年的天数差,最多循环 365 次(因为一年最多 365 天),属于O(1),再计算年份差的总天数,差几年就是几循环次数等于年份差 Y,也属于O(1),所以时间复杂度是O(1)
operator<< 重载ostream& operator<<(ostream& out, const Date& d)
{
out<<d._year<<' '<< d._month <<' '<< d._day<<endl;
return out;
}Date 向输出流(如 cout**)的打印**。ostream& out 是输出流对象(如 cout),通过引用传递以支持链式调用(如 cout << d1 << d2;)。const Date& d 是要打印的 Date 对象,加 const 和引用是为了避免拷贝且保证对象不被修改。Date 的成员 _year、_month、_day,最后返回 out 以支持链式操作。operator>> 重载istream& operator>>(istream& in, Date& d)
{
int year, month, day;
in >> year >> month >> day;
if (month > 0 && month < 13 && day > 0 && day <= d.GetMonthDay(year, month))
{
d._year = year;
d._month = month;
d._day = day;
}
else
{
cout << "非法日期" << endl;
assert(0);
}
return in;
}cin**)读取数据并赋值给** Date 对象,同时进行日期合法性校验。istream& in 是输入流对象(如 cin),引用传递支持链式调用(如 cin >> d1 >> d2;)。Date& d 是要赋值的 Date 对象,引用传递以直接修改其成员。year、month、day 三个整数。if 判断日期合法性: month 需在 1-12 之间;day 需大于 0,且不超过当月最大天数(由 d.GetMonthDay(year, month) 函数判断,该函数需实现 “判断某年某月有多少天” 的逻辑,比如二月闰年 29 天、平年 28 天等)。d 的成员;若非法,输出 “非法日期” 并通过 assert(0) 断言终止程序(实际项目中可替换为更友好的错误处理)。in 支持链式输入。【注意】: ostream(如 cout)和 istream(如 cin)类不可拷贝,且为支持流运算符的链式调用,其重载的 operator<<和 operator>> 中对应的流参数及返回值必须用引用传递。