类和对象实操之【日期类】

简介: 在学完类和对象相关知识后,需要一个程序来供我们练习、巩固知识点,日期类就是我们练习的首选程序,日期类实现简单且功能丰富,相信在完整地将日期类实现后,能对类和对象有更好的掌握及更深的理解

✨个人主页: Yohifo

🎉所属专栏: C++修行之路

🎊每篇一句: 图片来源


The pessimist complains about the wind; the optimist expects it to change; the realist adjusts the sails.


悲观主义者抱怨风;乐观主义者期望它改变;现实主义者调整风帆。



dceebf58cdcbfc0c14dad3c5c05837e.png

🗓️前言


在学完类和对象相关知识后,需要一个程序来供我们练习、巩固知识点,日期类就是我们练习的首选程序,日期类实现简单且功能丰富,相信在完整地将日期类实现后,能对类和对象有更好的掌握及更深的理解

d9e920627c298090d66ce7200f88db7.png



🗓️正文


为了更符合工程标准,这里采用三个文件的方式实现程序


用于声明类和方法的 .h 头文件


Date.h


用于实现类和方法的 .cpp 源文件


Date.cpp

用于测试功能的 .cpp 源文件

test.cpp

7a9aebce993fd04dfb8dbe3947a93f5.png

📆类的定义

先简单定义一下每个类中都有的默认成员函数


//当前位于文件 Date.h 中#pragma once#include<iostream>usingstd::cout;    //采用部分展开的方式usingstd::cin;
//采用命名空间namespaceYohifo{
classDate    {
public:
//构造函数,频繁使用且短小的代码直接在类的声明中实现,成为内联函数Date(intyear=2023, intmonth=2, intday=11)
    :_year(year)
    , _month(month)
    , _day(day)
  {}
//拷贝构造函数Date(constDate&d)
  {
_year=d._year;
_month=d._month;
_day=d._day;
  }
//赋值重载函数Date&operator=(constDate&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

月在区间 [1, 12] 内,超过为非法

根据年月推算出天数,天数不能操作规定天数,也不能 <= 0

注意:


当前包括后续函数都是采取先在头文件 Date.h 的类中声明,再到 Date.cpp 实现的路径

因历史原因导致的闰年变动这里不考虑,该程序实现的是理想情况下的闰年状态

程序计算范围覆盖至公元前,限度为 [INT_MIN, INT_MAX]

#include"Date.h"usingnamespaceYohifo; //全局展开命名空间//合法性检验boolDate::check() const{
//年不能为0年if (_year==0)
returnfalse;
//月份区间 [1, 12]if (_month<1||_month>12)
returnfalse;
//天数要合理// getMonthDay 函数后续实现if (_day<1||_day>getMonthDay())
returnfalse;
returntrue;
}


📅判断闰年


闰年二月多一天,因此需要特殊处理


闰年判断技巧: 四年一闰且百年不闰 或者 四百年一闰


//闰年判断boolDate::checkLeapYear() const{
//按照技巧判断if (((_year%4==0) && (_year%100!=0)) || (_year%400==0))
returntrue;
elsereturnfalse;
}


📅获取年份天数


闰年多一天,为 366 ,非闰年为 365,判断返回即可


//获取年份天数intDate::getYearDay() const{
//复用代码return (checkLeapYear() ?366 : 365);
}


📅获取月份天数


根据当前年份和月份,判断当月有多少天


注意: 闰年的二月需要特殊处理


//获取月份天数intDate::getMonthDay() const{
//非闰年情况下每个月天数,其中下标代表月份intarr[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
//如果为2月,且为闰年,直接返回 2月+1天if (_month==2&&checkLeapYear())
returnarr[_month] +1;
elsereturnarr[_month];
}


📆运算符重载


前面学习了 operator 运算符重载,现在正好可以拿来练练手


📅判断等于


两个日期相等的前提是 年、月、日都相等


//运算符重载//判断等于boolDate::operator==(constDate&d) const{
return ((_year==d._year) && (_month==d._month) && (_day==d._day));
}


📅判断小于


注意: 我们的运算顺序都是 左操作数、右操作数,其中隐含的 this 指针默认为 左操作数


*this 小于 d 的逻辑


首选判断年是否小于

年相等,判断月是否小于

年相等,月相等,判断天是否小于

//判断小于boolDate::operator<(constDate&d) const{
if (_year<d._year)    //判断年returntrue;
elseif (_year==d._year&&_month<d._month) //判断月returntrue;
elseif (_year==d._year&&_month==d._month&&_day<d._day)   //判断天returntrue;
elsereturnfalse;


📅复用至所有判断


善用代码复用,有了等于和小于,我们可以直接写出所有判断


//判断不等于boolDate::operator!=(constDate&d) const{
return!(*this==d);   //等于,取反为不等于}
//判断小于等于boolDate::operator<=(constDate&d) const{
//小于、等于成立一个即可return ((*this<d) || (*this==d));
}
//判断大于boolDate::operator>(constDate&d) const{
//即不小于,也不等于return (!(*this<d) &&!(*this==d));
}
//判断大于等于boolDate::operator>=(constDate&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 内//新增几个局部展开usingstd::ostream;
usingstd::istream;
usingstd::endl;
namespaceYohifo{
classDate    {
//声明为类的友元函数friendstd::ostream&operator<<(std::ostream&out, constDate&d2);
friendstd::istream&operator>>(std::istream&in, Date&d2);  //注意//……    };
//直接定义在头文件中,成为内联函数inlineostream&operator<<(ostream&out, constDate&d)
    {
//此时需要检验日期合法性if (Date(d).check() ==false)
  {
out<<"警告,当前日期非法!"<<endl;
out<<"后续操作将会受到限制"<<endl;
  }
out<<d._year<<"年"<<d._month<<"月"<<d._day<<"日"<<endl;
returnout;
    }
inlineistream&operator>>(istream&in, Date&d)
    {
Datetmp;
flag:
cout<<"请入日期,格式为:年 月 日"<<endl;
in>>tmp._year>>tmp._month>>tmp._day;
//如果输入日期非法,就重新输入if (Date(tmp).check() ==false)
  {
cout<<"警告,当前日期非法!"<<endl;
cout<<"日期输入失败,请尝试重新输入!"<<endl;
gotoflag;
  }
cout<<"输入成功!"<<endl;
returnin;
    }
}


有了这两个运算符重载后,我们就可以直接对自定义类型(日期类对象)直接进行输入输出操作了


Dated1;
cin>>d1;  //对自定义类型的输入cout<<d1; //对自定义类型的输出


📆日期+=天数


下面涉及两个重要算法


日期 += 天数

日期 -= 天数

这里把 日期 += 天数 介绍清楚了,日期 -= 天数 就很好写了,就是倒着走


有了 日期 += 天数 后,可以直接实现 日期 + 天数

同理也可以实现 日期 - 天数


📅核心思想


注:此时实现的是 日期+=天数


进位思想:天数满了后进位到月份上,月份满后进位至年份上

a48633f303b5432c97e939b0246120bd.gif

注意:


每个月对应天数都需要计算,因为每个月都不同

月份为12月时,再+就变成了下一年的一月

假设为公元前,加至0年时,需要特殊处理为公元1年

+= 操作返回的是左操作数本身,应对 (d1 += 10) = 20 这种情况

📅代码实现



//日期+=天数Date&Date::operator+=(constintval)
{
if (check() ==false)
    {
cout<<"警告,当前日期非法,无法进行操作"<<endl;
return*this;
    }
//判断 val,避免恶意操作,如 d1 += -100if (val<0)
    {
//此时需要调用 -=*this-= (-val);
return*this;
    }
//因为是 += 不需要创建临时对象//首先把天数全部加至 _day 上_day+=val;
//获取当前月份天数intmonthDay=getMonthDay();
//判断进位,直至 _day <= monthDaywhile (_day>monthDay)
    {
//此时大于,先把多余的天数减掉_day-=monthDay;
//此时进位一个月++_month;
//判断月份是否大于 12if (_month>12)
  {
//此时需要进年++_year;
//月份变为1月_month=1;
//判断是否为0年if (_year==0)
_year=1;  //调整  }
//重新获取月份天数monthDay=getMonthDay();
    }
//返回 *this 本身return*this;
}


有了这个函数后,我们就可以根据当前日期推算 N 天后的日期


26d132fce648f73929e1d25eeac34c4.png

日期+天数 可以直接复用上面的代码,而 日期-=天数 将逻辑反过来就行了,这里不展示代码了,完整代码在文末的 gitee 仓库中


📆日期-日期


日期+日期无意义,但日期-日期有,可以计算两日期差值


日期相减有两种情况:


左操作数小于右操作数,此时返回大于0的值

左操作数大于右操作数,此时返回小于0的值

具体实现时也很好处理,直接用一个 flag 就行了


📅核心思想


先不管左右操作数大小,我们先找出较大操作数与较小操作数


通过较小操作数逐渐逼近较大操作数,其中经过的天数就是差值


步骤:


先把日期对齐,即小操作数日期与大操作数日期平齐

再把月份对齐

最后再把年份对齐就行了

随着步骤的深入,天数计算会越来越快的

除了这种方法外,我们还可以直接一天一天的加,直到相等,当然这种效率较低


📅代码实现

//日期 - 日期constintDate::operator-(constDate&d) const{
if (check() ==false||d.check() ==false)
    {
cout<<"警告,当前日期非法,无法进行操作!默认返回 0"<<endl;
return0;
    }
//假设右操作数为较大值Datemax(d);
Datemin(*this);
intflag=1;
//判断if (min>max)
    {
max=*this;
min=d;
flag=-1;
    }
//小的向大的靠近intdaySum=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++;
    }
returndaySum*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


后置操作是先记录值,再进行自加或自减,返回之前记录的值


//后置++constDateDate::operator++(int)
{
//借助临时变量Datetmp(*this);
*this+=1;
returntmp;
}
//后置--constDateDate::operator--(int)
{
Datetmp(*this);
*this-=1;
returntmp;
}


特别注意: 对于自定义类型来说,在进行自加、自减操作时,最好采用前置,因为后置会发生拷贝构造行为,造成资源浪费


📆程序源码


完整的代码在这里 Gitee

9ed005ef3262d242d586da1b2cd4090.png


🗓️总结


以上就是关于日期类实现的全部内容了,涉及到了前面学的大部分知识,希望大家在看完后能把它独立敲一遍,加深理解


如果你觉得本文写的还不错的话,可以留下一个小小的赞👍,你的支持是我分享的最大动力!


如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正



目录
相关文章
|
28天前
|
编译器 C++
【CPP】手把手教会日期类,日期类实现思路,详细思路
【CPP】手把手教会日期类,日期类实现思路,详细思路
|
4月前
|
编译器 C语言 C++
从C语言到C++⑥(第二章_类和对象_中篇_续)大练习(日期类)+笔试选择题(下)
从C语言到C++⑥(第二章_类和对象_中篇_续)大练习(日期类)+笔试选择题
45 2
从C语言到C++⑥(第二章_类和对象_中篇_续)大练习(日期类)+笔试选择题(下)
|
4月前
|
算法 编译器 C语言
从C语言到C++⑥(第二章_类和对象_中篇_续)大练习(日期类)+笔试选择题(上)
从C语言到C++⑥(第二章_类和对象_中篇_续)大练习(日期类)+笔试选择题
38 3
|
4月前
|
编译器 C语言 C++
从C语言到C++⑥(第二章_类和对象_中篇_续)大练习(日期类)+笔试选择题(中)
从C语言到C++⑥(第二章_类和对象_中篇_续)大练习(日期类)+笔试选择题
38 1
|
4月前
|
存储 编译器 C语言
【C++初阶(六)】类和对象(中)与日期类的实现
【C++初阶(六)】类和对象(中)与日期类的实现
50 1
|
4月前
|
编译器 C++
【C++类和对象】日期类的实现(上)
【C++类和对象】日期类的实现
|
4月前
|
存储 C++
【C++类和对象】日期类的实现(下)
【C++类和对象】日期类的实现
|
存储 编译器 C语言
【C++入门到精通】C++入门 —— 类和对象(了解类和对象)(二)
【C++入门到精通】C++入门 —— 类和对象(了解类和对象)
57 0
|
12月前
|
编译器 C++
C++类和对象 练习小项目---日期类的实现.
C++类和对象 练习小项目---日期类的实现.
77 0
|
存储 算法 程序员
【C++入门到精通】C++入门 —— 类和对象(了解类和对象)(一)
【C++入门到精通】C++入门 —— 类和对象(了解类和对象)
57 0