代码总览与总结
// 实现一个完善的日期类
class Date
{
public:
int GetMonthDay(int year, int month)
{
int monthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
// 闰年2月
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))
return 29;
return monthDays[month];
}
Date(int year = 0, int month = 1, 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(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool operator<(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;
}
bool operator<=(const Date& d)
{
// 函数原型为 operator<=(Date* this, const Date& d)
// 函数调用时相当于传参 operator<=(&d1, d2)
// 此处 *this = *(&d1),即d1
return *this < d || *this == d;
// 所以上面的代码等价于
// return d1 < d2 || d1 == d2;
}
// 通过 *this ,我们可以很轻松实现代码的复用
// 代码的复用有很多好处,当未来加入什么“时分秒”时不需要修改全部函数
// 只需要修改一开始的少数函数就可以达成修改全部函数的目的
// 复用是高内聚,写代码的提倡的就是低耦合高内聚
bool operator>(const Date& d)
{
return !(*this <= d);
}
bool operator>=(const Date& d)
{
return !(*this < d);
}
bool operator!=(const Date& d)
{
return !(*this == d);
}
// +
//Date operator+(int day)
//{
// Date ret(*this); // 拷贝构造一个ret
// ret._day += day;
// while (ret._day > GetMonthDay(ret._year, ret._month))
// {
// // 日期大于天数上限,往月进位
// ret._day -= GetMonthDay(ret._year, ret._month);
// ret._month++;
// // 检查月份合法性
// if (ret._month > 12)
// {
// ret._month = 1;
// ret._year++;
// }
// }
// return ret;
//}
// +
// 复用实现
Date operator+(int day)
{
Date ret(*this); // 拷贝构造一个ret
ret += day; // ret.operator+=(day)
return ret;
}
// +=
// 类对象本身在函数外,所以可以传引用返回
Date& operator+=(int day)
{
if (day < 0)
{
return *this -= -day;
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
// -
/*Date operator-(int day)
{
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;
}*/
// -
// 复用实现
Date operator-(int day)
{
Date ret(*this);
ret -= day; // operator-=(day)
return ret;
}
// -=
Date& operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
_month--;
if (_month <= 0)
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
// ++(前置++)
Date& operator++()
{
*this += 1;
return *this;
}
// ++(后置++)
// 为了构成函数重载,函数参数加了一个int,但实际不用传参数
// 这个int仅仅是编译器为了构成函数重载加上的
Date operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
// --(前置--)
Date& operator--()
{
*this -= 1;
return *this;
}
//--(后置--)
// 为了构成函数重载,函数参数加了一个int,但实际不用传参数
// 这个int仅仅是编译器为了构成函数重载加上的
Date operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
// d1 - d2
int operator-(const Date& d)
{
int flag = 1;
Date max = *this; // 拷贝构造
Date min = d;
if (*this < d)
{
max = d; // operator=
min = *this;
flag = -1;
}
int n = 0;
while (min != max)
{
++min; // 参考前置++与后置++的实现,自定义类型能用前置++就不要用后置
++n;
}
return n * flag;
}
// 赋值运算符重载
// d3 = d1
// 运算符的重载是为了让自定义类性可以像内置类型一样去使用运算符
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
1. 核心功能
日期计算:加减天数、递增递减(前置/后置)
日期比较:全部6种关系运算符(==, !=, <, <=, >, >=)
日期差值:计算两个日期之间相差的天数
2. 设计亮点
高内聚低耦合:通过运算符复用来减少代码冗余(例如,用
==
和<
实现其他所有比较运算符;用+=
实现+
)。鲁棒性:构造函数进行了日期合法性检查;加减操作处理了负数和跨年月的情况。
效率考虑:在日期差值计算中,使用小日期自增到大日期的方式,简单可靠。
3. 关键技术点
闰年的判断与二月份天数的动态获取。
前置与后置自增/自减运算符的重载区分。
赋值运算符的自我赋值检查。
对于
+=
和-=
,处理了传入负数的特殊情况,增强了接口的健壮性。
分段代码讲解
1. 辅助函数与构造函数
int GetMonthDay(int year, int month)
{
int monthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
// 闰年2月
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))
return 29;
return monthDays[month];
}
Date(int year = 0, int month = 1, 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;
// 注意:这里最好抛出一个异常,或者将日期设为一个确定的合法状态(如1970-1-1),而不是仅仅打印。
}
}
GetMonthDay
: 根据年份和月份返回对应的天数,核心是处理闰年二月的特殊情况。构造函数: 提供了全缺省参数。首先检查传入的日期是否合法,合法则初始化,否则输出错误信息。**建议改进**:在非法日期时进行错误处理(如抛出异常),而不仅仅是打印。
2. 拷贝构造与基本比较运算符
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool operator<(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;
}
拷贝构造函数: 进行深拷贝(虽然这里都是int,但习惯上称为深拷贝)。
operator==
: 严格比较年、月、日是否全部相等。operator<
: 实现了日期的字典序比较,是其他所有比较运算符的基石。
3. 复用实现的比较运算符
bool operator<=(const Date& d)
{
return *this < d || *this == d;
}
bool operator>(const Date& d)
{
return !(*this <= d);
}
bool operator>=(const Date& d)
{
return !(*this < d);
}
bool operator!=(const Date& d)
{
return !(*this == d);
}
复用思想:这是代码最大的亮点。只需要实现
==
和<
,其余四个运算符都可以通过逻辑组合轻松实现。这使得代码维护性极高,如需修改比较逻辑,只需改动==
或<
即可。
4. 复合赋值运算符(+=, -=)
Date& operator+=(int day)
{
if (day < 0) // 处理负数情况:加负天数等于减正天数
{
return *this -= -day;
}
_day += day;
while (_day > GetMonthDay(_year, _month)) // 当天数溢出时循环调整
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12) // 月份溢出,进年
{
_year++;
_month = 1;
}
}
return *this; // 返回自身的引用,以支持连续赋值(如 d1 += 5 += 10;)
}
Date& operator-=(int day)
{
if (day < 0) // 处理负数情况:减负天数等于加正天数
{
return *this += -day;
}
_day -= day;
while (_day <= 0) // 当天数变为0或负数时,需要借位
{
_month--;
if (_month <= 0) // 月份借完,向年借
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month); // 借来一个月的天数加到当前天数上
}
return *this;
}
核心算法:这两个函数是日期计算的核心。
+=
通过循环进位,-=
通过循环借位,正确处理了跨月、跨年的复杂情况。接口健壮性:处理了传入负数的边界情况,使接口更友好。
返回值:返回引用 (
Date&
),支持链式操作(如d1 += 10 -= 5;
),这是符合内置类型行为的标准做法。
5. 算术运算符(+, -)与递增递减(++, --)
// 复用 += 实现 +
Date operator+(int day)
{
Date ret(*this); // 创建当前对象的一个副本
ret += day; // 对副本进行操作
return ret; // 返回副本的结果
}
// 复用 -= 实现 -
Date operator-(int day)
{
Date ret(*this);
ret -= day;
return ret;
}
// 前置++:返回自增后的自身
Date& operator++()
{
*this += 1;
return *this;
}
// 后置++:返回自增前的值(副本)
Date operator++(int)
{
Date tmp(*this); // 保存原来的值
*this += 1; // 自身增加
return tmp; // 返回原来的值
}
// 前置--和后置--的实现逻辑与++完全相同
Date& operator--()
{
*this -= 1;
return *this;
}
Date operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
复用思想:
+
和-
运算符通过复用+=
和-=
实现,避免了重复的进位/借位逻辑。前后置区分:
前置:先计算,后返回自身的引用 (
Date&
)。后置:使用
int
参数作为占位符以区分重载。先保存原状态,再计算,最后返回原状态的副本 (Date
)。
6. 日期差值运算符(-)
int operator-(const Date& d)
{
int flag = 1;
Date max = *this;
Date min = d;
if (*this < d) // 确定大小关系,保证max是较大的日期
{
max = d;
min = *this;
flag = -1; // 如果this < d,结果应为负数
}
int n = 0;
while (min != max) // 让小日期不断自增,直到等于大日期
{
++min; // 使用前置++,效率更高
++n;
}
return n * flag; // 返回带符号的天数差
}
算法:采用朴素的“计数”方法,让小日期一天天加到大日期,计数器的值就是差值。方法简单正确,但对于相差很远的日子效率较低(可采用计算绝对日期数再相减的优化算法)。
符号处理:通过
flag
变量控制结果的符号,使d1 - d2
的结果与预期一致。
7. 赋值运算符与打印函数
Date& operator=(const Date& d)
{
if (this != &d) // 防止自我赋值(如 d1 = d1;)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this; // 返回自身引用,支持链式赋值(d1 = d2 = d3;)
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
赋值运算符:是拷贝构造的伴侣。注意**自我赋值检查** (
if (this != &d)
),这是一个重要的安全措施。Print函数:一个简单的格式化输出功能。
总结
这个 Date
类清晰地展示了:
如何为自定义类型重载运算符,使其行为与内置类型一致。
代码复用的强大威力,极大地减少了代码量和维护成本。
边界条件处理的重要性(如闰年、日期合法性、负数操作、自我赋值等)。
可以进行的改进点:
构造函数在遇到非法日期时,可以抛出异常 (
std::invalid_argument
) 而不是仅输出信息。operator-
(计算差值) 的算法可以优化为计算每个日期相对于某个固定日期(如0000-01-01)的天数,然后相减,以提升效率。可以添加
const
成员函数,如void Print() const
和bool operator<(const Date& d) const
,表示这些函数不会修改对象状态,可以在常量对象上调用。