C++ 日期类接口实现与 const 成员函数深度解析:this 指针的只读约束



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

目录
一、const成员
二、日期类的默认成员函数
三、获得某年某月的天数
四、日期加天数
【思考】:先实现operator+=,还是先实现operator+
五、日期减天数
六、前置++、--,后置++、--
七、运算符重载
八、日期减日期
九、流插入和流提取运算符重载
9.1 流插入运算符 operator<< 重载
9.2 流提取运算符 operator>> 重载
一、const成员
由于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个问题:
- const对象可以调用非const成员函数吗?
- 非const对象可以调用const成员函数吗?
- const成员函数内可以调用其它的非const成员函数吗?
- 非const成员函数内可以调用其它的const成员函数吗?
- 不可以,因为权限不能放大
- 可以,因为权限可以缩小
- 不可以,因为权限不能放大
- 可以,因为权限可以缩小
二、日期类的默认成员函数
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];
}
- days数组下标为0的位置放了个0,这点很细节,这个时候我们的月份和下标就直接可以对应上了
- 闰年的二月是29天,所以得单独做判断
四、日期加天数
// 日期+=天数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的数值处于当前月的天数范围内时,停止调整,完成日期的进位计算。- +操作直接复用+=即可,但是要记住不能返回引用,因为+的时候,*this指针所指向的对象是不能改变的,所以返回的是临时构造的对象,因此不能返回引用
【思考】:先实现operator+=,还是先实现operator+

从图中可以看到,先+后+=比先+=后+多了一次赋值运算符重载,所以,先+=后+效率更高
五、日期减天数
这个接口的效率分析和日期加天数是没区别的,所以依旧先实现-=,在复用-=
// 日期-天数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;// 区分大小日期,修正flagif (*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)
九、流插入和流提取运算符重载
9.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以支持链式操作。
- 第一个参数
9.2 流提取运算符 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>> 中对应的流参数及返回值必须用引用传递。

