✨个人主页: Yohifo
所属专栏: C++修行之路
每篇一句: 图片来源
在学完类和对象
相关知识后,需要一个程序来供我们练习、巩固知识点,日期类
就是我们练习的首选程序,日期类
实现简单且功能丰富,相信在完整地将日期类
实现后,能对类和对象
有更好的掌握及更深的理解
为了更符合工程标准,这里采用三个文件的方式实现程序
用于声明类和方法的 .h
头文件
Date.h
用于实现类和方法的 .cpp
源文件
Date.cpp
用于测试功能的 .cpp
源文件
test.cpp
先简单定义一下每个类中都有的默认成员函数
//当前位于文件 Date.h 中
#pragma once
#include
using std::cout; //采用部分展开的方式
using std::cin;
//采用命名空间
namespace Yohifo
{
class Date
{
public:
//构造函数,频繁使用且短小的代码直接在类的声明中实现,成为内联函数
Date(int year = 2023, int month = 2, int day = 11)
:_year(year)
, _month(month)
, _day(day)
{}
//拷贝构造函数
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//赋值重载函数
Date& operator=(const Date& d)
{
if (this == &d)
return *this;
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
//析构函数
~Date()
{
_year = 1970;
_month = 2;
_day = 11;
}
private:
int _year; //年、月、日
int _month;
int _day;
};
}
首先编写第一个函数:合法性检验
检验标准
<= 0
注意:
Date.h
的类中声明,再到 Date.cpp
实现的路径[INT_MIN, INT_MAX]
#include"Date.h"
using namespace Yohifo; //全局展开命名空间
//合法性检验
bool Date::check() const
{
//年不能为0年
if (_year == 0)
return false;
//月份区间 [1, 12]
if (_month < 1 || _month > 12)
return false;
//天数要合理
// getMonthDay 函数后续实现
if (_day < 1 || _day > getMonthDay())
return false;
return true;
}
闰年二月多一天,因此需要特殊处理
闰年判断技巧: 四年一闰且百年不闰 或者 四百年一闰
//闰年判断
bool Date::checkLeapYear() const
{
//按照技巧判断
if (((_year % 4 == 0) && (_year % 100 != 0)) || (_year % 400 == 0))
return true;
else
return false;
}
闰年多一天,为 366
,非闰年为 365
,判断返回即可
//获取年份天数
int Date::getYearDay() const
{
//复用代码
return (checkLeapYear() ? 366 : 365);
}
根据当前年份和月份,判断当月有多少天
注意: 闰年的二月需要特殊处理
//获取月份天数
int Date::getMonthDay() const
{
//非闰年情况下每个月天数,其中下标代表月份
int arr[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
//如果为2月,且为闰年,直接返回 2月+1天
if (_month == 2 && checkLeapYear())
return arr[_month] + 1;
else
return arr[_month];
}
前面学习了 operator
运算符重载,现在正好可以拿来练练手
两个日期相等的前提是 年
、月
、日
都相等
//运算符重载
//判断等于
bool Date::operator==(const Date& d) const
{
return ((_year == d._year) && (_month == d._month) && (_day == d._day));
}
注意: 我们的运算顺序都是 左操作数
、右操作数
,其中隐含的 this
指针默认为 左操作数
*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;
else
return false;
}
善用代码复用,有了等于和小于,我们可以直接写出所有判断
//判断不等于
bool Date::operator!=(const Date& d) const
{
return !(*this == d); //等于,取反为不等于
}
//判断小于等于
bool Date::operator<=(const Date& d) const
{
//小于、等于成立一个即可
return ((*this < d) || (*this == d));
}
//判断大于
bool Date::operator>(const Date& d) const
{
//即不小于,也不等于
return (!(*this < d) && !(*this == d));
}
//判断大于等于
bool Date::operator>=(const Date& d) const
{
//大于或等于
return ((*this > d) || (*this == d));
}
cout
、cin
只能输出、输出内置类型,但如果我们对它进行改造一下,就能直接输出我们的自定义类型
注意:
cout
类型为 ostream
,cin
类型为 istream
cout
、cin
变为重载后的左操作数,此时的运算符重载就不能写在类内,因为在类中的函数默认 this
为第一个参数,即左操作数友元函数
cout
、cin
本身,避免出现 cout << d1 << d2
这种情况此时可以利用合法性检验了
实现 operator>> 时,右操作数不能用 const 修饰
//在 Date.h 内
//新增几个局部展开
using std::ostream;
using std::istream;
using std::endl;
namespace Yohifo
{
class Date
{
//声明为类的友元函数
friend std::ostream& operator<<(std::ostream& out, const Date& d2);
friend std::istream& operator>>(std::istream& in, Date& d2); //注意
//……
};
//直接定义在头文件中,成为内联函数
inline ostream& operator<<(ostream& out, const Date& d)
{
//此时需要检验日期合法性
if (Date(d).check() == false)
{
out << "警告,当前日期非法!" << endl;
out << "后续操作将会受到限制" << endl;
}
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
inline istream& operator>>(istream& in, Date& d)
{
Date tmp;
flag:
cout << "请入日期,格式为:年 月 日" << endl;
in >> tmp._year >> tmp._month >> tmp._day;
//如果输入日期非法,就重新输入
if (Date(tmp).check() == false)
{
cout << "警告,当前日期非法!" << endl;
cout << "日期输入失败,请尝试重新输入!" << endl;
goto flag;
}
cout << "输入成功!" << endl;
return in;
}
}
有了这两个运算符重载后,我们就可以直接对自定义类型(日期类对象)直接进行输入输出操作了
Date d1;
cin >> d1; //对自定义类型的输入
cout << d1; //对自定义类型的输出
下面涉及两个重要算法
这里把 日期 += 天数
介绍清楚了,日期 -= 天数
就很好写了,就是倒着走
日期 += 天数
后,可以直接实现 日期 + 天数
日期 - 天数
注:此时实现的是 日期+=天数
进位思想:天数满了后进位到月份上,月份满后进位至年份上
注意:
+=
操作返回的是左操作数本身,应对 (d1 += 10) = 20
这种情况//日期+=天数
Date& Date::operator+=(const int val)
{
if (check() == false)
{
cout << "警告,当前日期非法,无法进行操作" << endl;
return *this;
}
//判断 val,避免恶意操作,如 d1 += -100
if (val < 0)
{
//此时需要调用 -=
*this -= (-val);
return *this;
}
//因为是 += 不需要创建临时对象
//首先把天数全部加至 _day 上
_day += val;
//获取当前月份天数
int monthDay = getMonthDay();
//判断进位,直至 _day <= monthDay
while (_day > monthDay)
{
//此时大于,先把多余的天数减掉
_day -= monthDay;
//此时进位一个月
++_month;
//判断月份是否大于 12
if (_month > 12)
{
//此时需要进年
++_year;
//月份变为1月
_month = 1;
//判断是否为0年
if (_year == 0)
_year = 1; //调整
}
//重新获取月份天数
monthDay = getMonthDay();
}
//返回 *this 本身
return *this;
}
日期+天数
可以直接复用上面的代码,而日期-=天数
将逻辑反过来就行了,这里不展示代码了,完整代码在文末的 gitee 仓库中
日期+日期无意义,但日期-日期有,可以计算两日期差值
日期相减有两种情况:
左操作数
小于右操作数
,此时返回大于0的值左操作数
大于右操作数
,此时返回小于0的值具体实现时也很好处理,直接用一个 flag
就行了
先不管左右操作数大小,我们先找出较大操作数与较小操作数
通过较小操作数逐渐逼近较大操作数,其中经过的天数就是差值
步骤:
除了这种方法外,我们还可以直接一天一天的加,直到相等,当然这种效率较低
//日期 - 日期
const int Date::operator-(const Date& d) const
{
if (check() == false || d.check() == false)
{
cout << "警告,当前日期非法,无法进行操作!默认返回 0" << endl;
return 0;
}
//假设右操作数为较大值
Date max(d);
Date min(*this);
int flag = 1;
//判断
if (min > max)
{
max = *this;
min = d;
flag = -1;
}
//小的向大的靠近
int daySum = 0;
//考虑天
while (min._day != max._day)
{
min += 1;
daySum++;
}
//考虑月
while (min._month != max._month)
{
daySum += min.getMonthDay();
min += min.getMonthDay();
}
//考虑年
while (min._year != max._year)
{
daySum += min.getYearDay();
min._year++;
}
return daySum * flag;
}
这种方法(同轴转动)将会带来一定的性能提升(相对逐天相加来说)
方法 | 相差 1k 年 | 相差 1w 年 | 相差 10w 年 |
---|---|---|---|
同轴转动 | 耗时 0 ms | 耗时 0 ms | 耗时 2 ms |
逐天相加 | 耗时 28 ms | 耗时 297 ms | 耗时 3142 ms |
注:实际差异与电脑性能有关
自加操作实现很简单,不过需要注意编译器是如何区分两者的
占位参数
占位参数
加在后置运算符重载中前置直接复用前面 +=
的代码
前置操作是先进行自加或自减,再返回
//前置++
Date& Date::operator++()
{
//直接复用
*this += 1;
return *this;
}
//前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
此时需要借助 占位参数
,当启用时,编译器会自动传参,并自动区分,占位参数
类型为 int
后置操作是先记录值,再进行自加或自减,返回之前记录的值
//后置++
const Date Date::operator++(int)
{
//借助临时变量
Date tmp(*this);
*this += 1;
return tmp;
}
//后置--
const Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
特别注意: 对于自定义类型来说,在进行自加、自减操作时,最好采用前置
,因为后置会发生拷贝构造
行为,造成资源浪费
以上就是关于日期类实现的全部内容了,涉及到了前面学的大部分知识,希望大家在看完后能把它独立敲一遍,加深理解
如果你觉得本文写的还不错的话,可以留下一个小小的赞,你的支持是我分享的最大动力!
如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正
…
相关文章推荐
类和对象合集系列
类和对象(下)
类和对象(中)
类和对象(上)
===============
C++入门必备
C++入门基础