欢迎光临
我们一直在努力

C++ Date Calculator --- 日期类计算器

        由于种种原因已经很久没有写博客了,最近刚学完C++类和对象,所以决定将印象比较深刻的

日期类计算器作为回归之作:

一.引言

        本文的核心内容:旨在基于C++实现一个功能完整的日期类,涵盖运算符重载、日期计算等核心功能。依旧同之前一样采用函数封装的形式,对类的结构设计核心功能实现以及测试用例设计与验证这三部分分别实现。

二.日期类核心需求分析

  • 基础功能:日期合法性校验(闰年 / 平年、年、月、日范围、 2 月天数 的处理

  • 比较运算符:<、==、<=、>、>=、!= 重载,支持日期大小判断

  • 算术运算符:

    • 日期 + 天数、日期 += 天数(前置 / 后置 ++

    • 日期 – 天数、日期 -= 天数(前置 / 后置 —

    • 两个日期相减,计算间隔天数

  • 输入输出:重载 <<、>>,支持日期的便捷打印与输入

三.日期类设计与实现(代码部分)

        为了增强文章的可读性,我将按照核心功能实现类的结构设计测试用例设计与验证这样的顺序依次介绍,实际撰写顺序根据个人习惯而定。

(一)核心功能实现(Date.cpp)

1.日期合法性校验:

构造函数+GetMonthDay函数(静态数组存储每月天数、闰年判断)

//构造函数:
Date::Date(int year, int month, int day)
{
if (month > 0 && month < 13
&& day>0 && day <= GetMonthDay(year, month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "非法日期" << endl;
assert(false);
}
}

核心逻辑1:
  • 1.月份必须在1-12之间;
  • 2.日期必须在“当月天数”范围之内,通过GetMonthDay函数动态获取;
  • 3.非法处理:快速定位非法日期(如2023-12-32等),打印提示并终止程序;

//GetMonthDay函数:
int Date::GetMonthDay(int year, int month)
{
//优化:局部静态,会被频繁调用,局部静态可以防止频繁创建;
static int daysArr[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;
}
else
{
return daysArr[month];
}
}

核心逻辑2:
  • 1.静态数组:下标0弃用,下标1-12对应1-12月的默认天数;
  • 2.妙用:static修饰,数组仅初始化一次,可以防止频繁创建,提高性能;

2.比较运算符重载:基于基础运算符复用

假设本次整个程序旨在实现以下功能:

< == <=
> >= !=
+= + ++(前置)
++(后置) -=
–(前置) –(后置) -(日期间减法)

        以上功能已经几乎囊括了所有的日期类计算功能,当然也可自行补充。当我们一个一个去实现以上函数时,会略显繁琐,那么是否有更加简便的方法呢,这就涉及到函数复用实现,我们可以先基础实现其中几个函数,假设我先实现<、==这两个函数,那么剩余的<=、>、>=、!=都可以用前两个函数复用实现。        

(1)基础实现:<、==

bool Date::operator<(const Date& x) const
{
if (_year < x._year)
{
return true;
}
else if (_year == x._year && _month < x._month)
{
return true;
}
else if (_year == x._year && _month == x._month && _day < x._day)
{
return true;
}

return false;
}

bool Date::operator==(const Date& x) const
{
return _year == x._year
&& _month == x._month
&& _day == x._day;
}

(2)复用实现:<=、>、>=、!=

bool Date::operator<=(const Date& x) const
{
return *this < x || *this == x;
}

bool Date::operator>(const Date& x) const
{
return !(*this <= x);
}

bool Date::operator>=(const Date& x) const
{
return !(*this < x);
}

bool Date::operator!=(const Date& x) const
{
return !(*this == x);
}

核心逻辑3:
  • 1.通过“取反/组合”复用,减少冗余代码,降低维护成本;
  • 2.*this本质是对隐式this指针的解引用,代表调用成员函数的对象本体;

3.日期加法:+=、+、++(前置、后置)

(1)operator+= & operator+

Date& Date::operator+=(int day)
{
if (day < 0)
{
return *this -= -day;
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}

Date Date::operator+(int day) const
{
Date tmp(*this);
tmp += day; //优化:复用+=

return tmp;
//tmp._day += day;
//while (tmp._day > GetMonthDay(tmp._year, tmp._month))
//{
//tmp._day -= GetMonthDay(tmp._year, tmp._month);
//++tmp._month;
//if (tmp._month == 13)
//{
//++tmp._year;
//tmp._month = 1;
//}
//}
//return tmp;
}

核心对比表1:
对比维度operator+=operator+
核心功能 原地增加天数,修改原对象 计算新日期,原对象保持不变
返回值类型 Date&(自身引用) Date(新对象值)
对原对象影响 直接修改 无任何修改
链式调用 支持(如 d1 += 5 += 3) 仅可作为右值链式计算(如 d1+5+3)
性能 无拷贝,性能高 至少 1 次拷贝,性能稍低
实现逻辑 核心加法逻辑(跨月 / 负数处理) 拷贝原对象后复用 += 实现
典型使用场景 需修改原日期(如日程顺延) 需保留原日期,获取计算结果
代码联系 作为加法运算的核心实现,被 operator+ 复用

     基于 operator+= 实现,无独立的加法逻辑

                   (注释部分为独立实现)

代码差别 直接操作当前对象的 _year/_month/_day,无对象拷贝 拷贝原对象到临时变量,调用 += 后返回新对象
关键代码解析

1. 负数天数转 -= 处理;

2. _day += day 后通过 while 循环处理跨月 / 跨年,逐月扣减天数直至日期合法;

3. 返回 *this(自身引用)

1. Date tmp(*this):拷贝构造临时对象,完全复制原对象的 _year/_month/_day;

2. tmp += day:复用 += 的核心逻辑完成临时对象的日期计算;

3. return tmp:返回计算后的临时对象,原对象不受影响;

4. 函数后 const:强制保证原对象(*this)不被修改

(2)operator++前置 & operator++后置

//涉及到前置++、后置++,默认前置++
Date& Date::operator++()
{
*this += 1;
return *this; //前置++返回++之后的值
}

//后置++:
//增加int这个参数不是为了具体的值,只是为了避免函数名相同,仅仅是占位
//跟前置++构成重载
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;

return tmp;
}

核心对比表2:
对比维度前置 ++(operator++())后置 ++(operator++(int))
核心功能 先将日期 + 1,再返回修改后的对象 先返回原日期,再将对象 + 1
参数特征 无参数 带 int 占位参数(仅用于重载区分,无实际意义)
返回值类型 Date&(自身引用) Date(原状态的临时对象)
对原对象影响 直接修改(日期 + 1) 直接修改(日期 + 1)
链式调用 支持(如 ++d1 += 5) 不支持链式修改(如 d1++ = 5 编译报错)
性能 无拷贝,性能高 需拷贝原对象,性能稍低
实现逻辑 复用 *this += 1,直接返回自身引用 1. 拷贝原对象到临时变量;2. *this += 1;3. 返回临时变量
典型使用场景 需优先使用修改后的值(如 ++d < 日期) 需先使用原值,再修改(如 d++ < 日期)
代码联系 作为自增核心实现,后置 ++ 复用其日期 + 1 逻辑 基于前置 ++ 的核心逻辑(+=1),增加拷贝和返回原状态的步骤
代码差别 无对象拷贝,直接修改后返回自身 先拷贝保留原状态,修改后返回原状态的临时对象
关键代码解析 *this += 1; return *this:直接修改当前对象,返回引用保证链式调用

1. Date tmp = *this:拷贝原对象保留初始状态;

2. *this += 1:复用 += 逻辑完成自增;

3. return tmp:返回修改前的原对象

4.日期减法:-=、-、–(前置、后置)

(1)operator-= & operator-

Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
–_month;
if (_month == 0)
{
_month = 12;
–_year;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}

Date Date::operator-(int day) const
{
Date tmp(*this);
tmp -= day;

return tmp;
}

(2)operator–前置 & operator–后置

//前置–:
Date& Date::operator–()
{
*this -= 1;
return *this;
}

//后置–:
Date Date::operator–(int)
{
Date tmp = *this;
*this -= 1;

return tmp;
}

对比同上;

5.日期相减:operator-(两个日期)

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;
}

核心逻辑4:
  • 1.统一计算方向:先确定大小日期(max 存大日期,min 存小日期),避免处理 “负数天数” 的循环;

  • 2.计数规则:小日期每次 ++,计数器 n++,直到 min == max,最终 n * flag 得到带符号的间隔天数(如 d1(2023-04-26) – d2(2024-04-26) 返回 -366,d2 – d1 返回 366);

6.全局输入输出重载:

ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;

return out;
}

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(false);
}
return in;
}

核心对比表3:
对比维度operator<<(输出运算符)operator>>(输入运算符)
核心功能 格式化输出日期对象的年 / 月 / 日 读取用户输入的年 / 月 / 日,赋值并校验日期合法性
函数类型 全局函数(配合友元访问私有成员) 全局函数(配合友元访问私有成员)
参数特征 ostream& out + const Date& d(d 不可改) istream& in + Date& d(d 需修改)
返回值类型 ostream&(流对象引用) istream&(流对象引用)
对原对象影响 仅读取,无任何修改 校验合法后修改,非法则断言终止
链式调用 支持(如 cout << d1 << d2) 支持(如 cin >> d1 >> d2)
实现逻辑 直接输出 d._year/_month/_day,无校验

1. 读取年 / 月 / 日到临时变量;

2. 校验日期合法性;

3. 合法则赋值给 d,非法则终止

典型使用场景 打印 / 展示日期(如日志、结果输出) 接收用户输入日期(如业务录入、交互场景)
代码联系 均为全局函数,依赖友元访问 Date 私有成员 均为全局函数,依赖友元访问 Date 私有成员
代码差别 仅读操作,无需校验,逻辑简单 读写操作,必须校验,非法日期需处理(断言)
关键代码解析 out << d._year << "年" << d._month << "月" << d._day << "日":格式化输出,返回 out 支持链式调用

1. in >> year >> month >> day:读取输入值;

2. 调用 d.GetMonthDay 校验天数;

3. 合法则赋值给 d,否则断言;

4. 返回 in 支持链式输入

(二)类的结构设计(Date.h)

写完Date.cpp内函数之后,就可以把相应的函数放进Date.h中进行声明:

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

class Date
{
//友元函数声明:
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);

public:
Date(int year = 1, int month = 1, int day = 1);

void Print() const //加上const之后,普通对象和const对象都可以调用;
{
cout << _year << "-" << _month << "-" << _day << endl;
}

bool operator<(const Date& x) const;
bool operator==(const Date& x) const;
bool operator<=(const Date& x) const;
bool operator>(const Date& x) const;
bool operator>=(const Date& x) const;
bool operator!=(const Date& x) const;

int GetMonthDay(int year, int month);

Date& operator+=(int day);
Date operator+(int day) const;
Date& operator++();
Date operator++(int);
Date& operator-=(int day);
Date operator-(int day) const;
Date& operator–();
Date operator–(int);

int operator-(const Date& d) const;

private:
int _year;
int _month;
int _day;
};

//流插入:
ostream& operator<<(ostream& out, const Date& d);
//流输出:
istream& operator>>(istream& in, Date& d);

要点:

  • 1.友元函数声明:

//友元函数声明:
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);

  •  因为operator<< / operator>> 是全局函数(非成员函数),但需要访问 Date 类的私有成员(_year/_month/_day),所以要用友元。其在类内部进行声明,通常放在开头,仅声明不实现
  • 2.构造函数:

Date(int year = 1, int month = 1, int day = 1);

  • 带默认参数:默认构造出 1年1月1日,支持三种调用方式:

    • Date d;(全用默认值);

    • Date d(2023);(year=2023,month/day 用默认);

    • Date d(2023, 4, 26);(全自定义)

  • 3.函数后置const:

void Print() const //加上const之后,普通对象和const对象都可以调用;
{
cout << _year << "-" << _month << "-" << _day << endl;
}

bool operator<(const Date& x) const;
bool operator==(const Date& x) const;
bool operator<=(const Date& x) const;
bool operator>(const Date& x) const;
bool operator>=(const Date& x) const;
bool operator!=(const Date& x) const;
Date operator+(int day) const;
Date operator-(int day) const;
int operator-(const Date& d) const;

  • 后置 const 是函数的 “只读” 标签:承诺不修改对象,编译器帮你兜底校验;
  • 只读操作的函数(如比较、取值、operator+)必须加 const,保证 const 对象能调用;
  • 修改操作的函数(如 +=、++、–)不能加 const,因为核心逻辑就是修改对象
  • 4.输入输出重载:

//流插入:
ostream& operator<<(ostream& out, const Date& d);
//流输出:
istream& operator>>(istream& in, Date& d);

  • operator<< /  operator>> 的第一个参数是 ostream& /  istream&(流对象),若作为成员函数,调用形式会变成 d1 << cout,不符合直觉,因此设计为全局函数,配合友元声明实现对私有成员的访问。

(三)测试用例设计与验证(Test.cpp)

#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"

void TestDate1()
{
Date d1(2023, 4, 26);
d1 += 100;
d1.Print();

Date d2(2023, 4, 26);
Date d3(d2 + 100); //Date d3 = d2 + 100;
d2.Print();
d3.Print();
}

void TestDate2()
{
Date d1(2023, 4, 26);
++d1;
d1.Print();
d1++;
d1.Print();
}

void TestDate3()
{
Date d1(2023, 4, 26);
d1 -= -25;
d1.Print();
d1 += -50;
d1.Print();
}

void TestDate4()
{
Date d1(2023, 4, 26);
d1–;
d1.Print();
–d1;
d1.Print();
}

void TestDate5()
{
Date d1(2023, 4, 26);
Date d2(2024, 4, 26);

cout << d1 – d2 << endl;
cout << d2 – d1 << endl;
}

void TestDate6()
{
Date d1(2023, 4, 26);
cout << d1;
operator<<(cout, d1);

Date d2(2024, 4, 26);
Date d3(2025, 4, 26);
cout << d2 << d3 << d1;

cin >> d2 >> d1;
cout << d2 << d1;
}
void TestDate7()
{
Date d2(2024, 4, 26);
d2.Print();
const Date d3(2025, 4, 26); //const主要涉及到权限的问题;
d3.Print();
d2 < d3;
cout << (d2 < d3) << endl;
d3 < d2;
cout << (d3 < d2) << endl;
}

int main()
{
//TestDate1();
//TestDate2();
//TestDate3();
//TestDate4();
//TestDate5();
//TestDate6();
TestDate7();

return 0;
}

这一部分主要是我自己写的几个测试函数,这个根据自己想测试的东西自行定义就行,我写的作为参考,就不加以赘述了……

赞(0)
未经允许不得转载:171主机测评 » C++ Date Calculator --- 日期类计算器
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址