C++类和对象下——实现日期类

 前言

        在学习了类和对象的六大成员函数后,为了巩固我们学习的知识可以手写一个日期类来帮助我们理解类和对象,加深对于其的了解。

默认函数

    构造函数

        既然是写类和对象,我们首先就要定义一个类,然后根据实际需要来加入类的数据与函数。作为一个日期类,肯定要有时间了,我们采用公元纪年法,存储三个变量,年 月 日,如下。

class Date
{private:int _year;int _month;int _day;
};

        注意这里将三个成员变量定义为private:,是为了防止用户在外面修改,提高安全性。其次这里在每个成员变量前面加了个下划线。 _year,这里是为了防止后面与函数的参数重名。当然重名也有其他解决方法例如用this.year,也可以。具体选那种看读者更适合那种编码风格。

        写完上面的变量后,我们第一个想到要写的函数必定是构造函数了。如下函数

Date(int year=1900, int month=1, int day=1)
{_year = year;_month = month;_day = day;
}

        注意我们这里写成了全缺省,这样做的好处是不用写无参的构造函数,给每个日期对象都有默认值。

拷贝构造函数

        其次我们要写的就是拷贝构造函数。注意这里的形参加了个const,这里加了const一是为了方防止修改形参d,二是为了通用性。如下例子

	Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}

        假如我们没有加const,有下述程序。这段代码会报错!为什么呢?d1的类型为const Date,而拷贝构造函数参数类型为Date& d,如果传参成功,是不是就可以在构造函数的内部修改原本为const类型的变量,违背了基本的定义语法。但用一个const Date类型拷贝初始化是我们需要的场景,为了让其正常运行,就需要在拷贝构造函数加const。

int main()
{const Date d1(2024, 4, 16);Date d2(d1);return 0;
}

        有const变量自然也有普通的变量了。我们就可以写个普通变量的拷贝构造函数。

Date(Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}

        其实在很多地方会将上面两个构造函数简化为一个,即保留含const的

Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}

        那自然有人问?普通变量的拷贝怎么办呢?在这里就不得不提C++对于变量类型的处理了。我们首先看段熟悉的代码。

int a=0;a=1.12;

        这段代码会报错么?为什么?大家可以在程序中运行下,结果是不会报错的,有的编译器可能会有警告。大家知道整型与浮点型在内存中的存储方式是不一样的,即使是对浮点型内存的截断读取a也不可能为1.

        但a的结果却是一,这是因为编译器帮我们做了类型转化,称之为隐式转换。如下图。

        同理当我们将Date变量赋给const Date& d,编译器也会额外开辟空间付给形参d。

        或许有读者又有疑问?Date变量赋给const Date& d可以,为什么const Date变量赋给Date& d不可以,因为前者是将内存的权限放小,而后者是将对内存的权限放大。在C++中将权限放小可以,但把权限放大就有可能产生难以预料的后果。

        接下来我们来实现与拷贝构造函数功能十分像的赋值重载函数。

赋值重载函数

        代码如下

	Date& operator=(const Date& d){if (&d != this){_year = d._year;_month = d._month;_day = d._day;}return *this;}

        这里我们任然将参数的类型加上const省去对于普通变量与const变量的分类了,当然对于普通变量会隐式转换,会减少些效率。

        注意这里将d的地址与this比较,这是为了防止自己和自己赋值的情况如 a=a,这样没有任何的意义。

析构函数

        在这个对象中我们没有开辟内存,没有在堆区申请空间,写不写析构函数都可以。

~Date()
{}

成员函数

<<重载

        我们为了后续的方便,首先要实现的便是cout输出Date类型,对于内置类型cout可以直接的输出,但是对于自定义类型要我们使用操作符重载.

        按照习惯我们极大概率会将<<写在类里面。写出如下的代码

ostream& operator<<(ostream& out)
{out << _year << "-" << _month << "-" << _day << endl;return out;
}

        其中的ostream参数是输出时要用到的一种类型,返回值为ostream是为了连续输出的原因。这个看起来没有什么错误,但运行的时候就会报错!

        我们明明重载了<<操作符,为什么却提示我们没有匹配类型呢?这就不得不提到this了,在使用cout << d1操作符重载的时候,我们从左向右显然要传递两个参数ostream和Date,在类中的成员函数默认第一个参数传递Date,即形参this指针,第二个实参初始化函数的形参。

        关于this指针详情可以看【C++ 类和对象 上 - CSDN App】http://t.csdnimg.cn/Wx5iO。在这里就不叙述了。

        于是上面的代码也不是不可以用,可以采用如下的方法使用

        但这种方法显然是不符合我们日常认知的。

        为了解决这种问题,我们把<<操作符改为全局函数。就可以解决顺序的问题了。如下代码

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

        但此时又有一个新的问题,这个重载函数不可以访问Date对象中私有的成员变量,这就体现了我们类的安全性高,为了解决我们需要把这个操作符重载函数声明为友元函数就可以了。

class Date
{
public:Date(int year=1900, int month=1, int day=1){_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 (&d != this){_year = d._year;_month = d._month;_day = d._day;}return *this;}friend ostream& operator<<(ostream& out, const Date& d);private:int _year;int _month;int _day;
};

        这样我们就可以正常输出了。

>>重载

        有了输出,当然要有其对应的输入最好。和输出重载一样,将>>操作符重载为全局函数,并且在类中声明为友元函数。

istream& operator>>(istream& in,  Date& d)
{in >> d._year >> d._month >> d._day;return in;
}

        如下图,刚开始输出1900时我们写的全缺省参数的作用,而后输出的就是我们输入的2024 5 13.

        光有输入输出函数显然是不可以的,我们也要有对应的函数。

大小比较

        对于一个日期比较大小是符合实际需求的,我们可以写个cmp成员,但更好的使用操作符重载,< >,这个我们最熟悉又可以减小记忆的负担。

>比较

        我们有两种方法比较两个日期的大小。

方法一

        不断地寻找true条件,最后剩下的就是false。注意年数相等的时候要判断月份

bool Date::operator>(Date& d)
{if (_year > d._year){return true;}else if (_year == d._year){if (_month > d._month){return true;}else if (_month == d._month){if (_day > d._day){return true;}}}return false;
}
方法二

        将日期的比较转换为数的比较。月份最多有12月,天最多有31天,我们就可以将年扩大10000倍,月扩大100倍,将2024年5月13日与2023年4月20日比较转换为2024513与2023420比较。我们知道数的比较大小是从高位往下开始比较的。这与我们比较日期的顺序不谋而合,就可以如下的简化代码。

bool Date::operator>(Date& d)
{return _day + _month * 100 + _year * 10000 > d._day + d._month * 100 + d._year * 10000;
}

同理小于等于。大于等于,小于都可以如上比较。

bool Date::operator<(Date& d)
{return _day + _month * 100 + _year * 10000 <d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator<=(Date& d)
{return _day + _month * 100 + _year * 10000 <=d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator>=(Date& d)
{return _day + _month * 100 + _year * 10000 >=d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator!=(Date& d)
{return _day + _month * 100 + _year * 10000 !=d._day + d._month * 100 + d._year * 10000;
}

        判断两个日期是否相等的代码也十分简单,如下。

相等判断

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

        到此我们的比较函数就写完了。但光有比较还不可以,我们可能想要知道50天后是哪一天,50天前是那一天,两个日期相差多少天。

加减操作

        在后续的操作中我们不可避免的要访问某年某月有多少天,我们便可以将他封装为成员函数,便于我们查找天数。

获取天数

        我们可以将每个月的天数写在一个数组中,然后哪一个月就读取哪一个数字,但其中2月十分特殊要分为润年的问题要单独判断下。

int Date::GetMonthDay(Date& d)
{static int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (d._month == 2 && ((d._year % 400 == 0) || (d._year % 4 == 0 && d._year % 100 != 0))){return 29;}return arr[d._month];
}

        这里将数组加上static 是为了避免重复创建,提高效率。然后就是判断是不是闰年的二月。

+=重载

        我们可以将一个日期类加上n天返回加n天后的日期。

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

        我们整体的循环是在找当前月合理的天数,如果不合理就月份加一,天数减去当前月,继续判断直到结束。

        测试结果正确。

        大家写的时候可以用日期计算器 - 天数计算器 | 在线日期计算工具 (sojson.com)这个网站检测。

+重载

        我们之前已经写完+=了,先在写+的思路是不是与+=类似,我们可以仿照+=写出+重载,但是我们还有更加简单的方法,复用+=!!

Date Date::operator+(int t)
{Date t1 = *this;t1 += t;return t1;
}

        这样复用代码就大大简化了我们写代码的复杂度。其实上面的判断也可以复用代码,读者可以自行尝试。

-=重载

        一个日期减去一个天数,与一个日前加上一个天数十分像。天数小于肯定是不合理的日期就加上当前月数。不断的循环判断直到合理数据。

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

-重载

        与之前一样,我们复用-=的函数。

Date Date::operator-(int t)
{Date tmp = *this;tmp -= t;return tmp;
}

        我们如果对+负数,-负数会怎么样?显然程序会崩溃但这又可能是我们使用者的操作,于是便可以在不同的重载函数互相调用。

        如下代码

Date Date::operator-(int t)
{if (t < 0)return *this + (-t);Date tmp = *this;tmp -= t;return tmp;
}

    日期相减

方法一

        我们当然会求两个日期之间的差数。便可以重载-。我们可以对小的天数一直加一直到二者相等。如下

int Date::operator-(Date& d)
{assert(*this >= d);Date t1 = *this;Date t2 = d;int t = 0;//一直加while (t1 != t2){t2 += 1;t++;}return t;
}

        我们有时也会写的前一个日期小,导致相差负数,为了达到这种效果也可以对上述代码稍加修改。

int Date::operator-(Date& d)
{if (*this < d)return d - *this;Date t1 = *this;Date t2 = d;int t = 0;//一直加while (t1 != t2){t2 += 1;t++;}return t;
}
方法二

        上述的代码十分简单,效率有些不理想,我们可以换种方法。

        首先我们先判断二者是否相同,不相同在判断t2的天数是否小于t1,小于说明可能只是天数不同,将天数加到t1的天数,然后判断是否相等。如果t1的天数等于t2的天数,说明月份不同,将t2的月份一次往上加一判断二者是否相等。

int Date::operator-(Date& d)
{if (*this < d)return d - *this;Date t1 = *this;Date t2 = d;int t = 0;//一直加while (t1 != t2){int c = GetMonthDay(t2);if (t2._day < t1._day && t1._day <= c){t += t1._day - t2._day;t2._day = t1._day;}else {t2._month++;if (t2._month == 13){t2._year++;t2._month = 1;}t += c - t2._day + 1;t2._day = 1;}}return t;
}

        这种方法的效率比第一种高了许多。

结语

        到这里本篇文章就结束了。喜欢的点点关注!

全部代码如下。

#include<iostream>
#include<assert.h>
using namespace std;class Date
{
public:Date(int year=1900, int month=1, int day=1){_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 (&d != this){_year = d._year;_month = d._month;_day = d._day;}return *this;}//友元函数声明friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);//比较函数bool operator==(Date& d);bool operator>(Date& d);bool operator<(Date& d);bool operator<=(Date& d);bool operator>=(Date& d);bool operator!=(Date& d);//加减操作函数Date& operator+=(int t);Date operator+(int t);Date& operator-=(int t);Date operator-(int t);int operator-(Date& d);int GetMonthDay(Date& d);private:int _year;int _month;int _day;
};//bool Date::operator>(Date& d)
//{
//	if (_year > d._year)
//	{
//		return true;
//	}
//	else if (_year == d._year)
//	{
//		if (_month > d._month)
//		{
//			return true;
//		}
//		else if (_month == d._month)
//		{
//			if (_day > d._day)
//			{
//				return true;
//			}
//		}
//	}
//	return false;
//}
//bool Date::operator<(Date& d)
//{
//	return !(*this >= d);
//}
//bool Date::operator<=(Date& d)
//{
//	return *this < d || d == *this;
//}
//bool Date::operator>=(Date& d)
//{
//	return *this > d || d == *this;
//}
//bool Date::operator!=(Date & d)
//{
//	return !(d == *this);
//}bool Date::operator>(Date& d)
{return _day + _month * 100 + _year * 10000 > d._day + d._month * 100 + d._year * 10000;
}bool Date::operator<(Date& d)
{return _day + _month * 100 + _year * 10000 <d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator<=(Date& d)
{return _day + _month * 100 + _year * 10000 <=d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator>=(Date& d)
{return _day + _month * 100 + _year * 10000 >=d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator!=(Date& d)
{return _day + _month * 100 + _year * 10000 !=d._day + d._month * 100 + d._year * 10000;
}
bool Date::operator==(Date& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}int Date::GetMonthDay(Date& d)
{static int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (d._month == 2 && ((d._year % 400 == 0) || (d._year % 4 == 0 && d._year % 100 != 0))){return 29;}return arr[d._month];
}Date& Date::operator+=(int t)
{if (t < 0)return *this -= (-t);_day += t;while (_day > GetMonthDay(*this)){_day -= GetMonthDay(*this);_month++;if (_month == 13){_month = 1;_year++;}}return *this;
}Date Date::operator+(int t)
{Date t1 = *this;t1 += t;return t1;
}Date& Date::operator-=(int t)
{if (t < 0)return *this += (-t);_day -= t;while ( _day<1 ){_month--;if (_month == 0){_month = 12;_year--;}_day += GetMonthDay(*this);}return *this;
}Date Date::operator-(int t)
{if (t < 0)return *this + (-t);Date tmp = *this;tmp -= t;return tmp;
}int Date::operator-(Date& d)
{if (*this < d)return d - *this;Date t1 = *this;Date t2 = d;int t = 0;//一直加while (t1 != t2){int c = GetMonthDay(t2);if (t2._day < t1._day && t1._day <= c){t += t1._day - t2._day;t2._day = t1._day;}else {t2._month++;if (t2._month == 13){t2._year++;t2._month = 1;}t += c - t2._day + 1;t2._day = 1;}}return t;
}ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}istream& operator>>(istream& in,  Date& d)
{in >> d._year >> d._month >> d._day;return in;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/696659.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Postman基础功能-断言与日志

若能脱颖而出&#xff0c;何必苦苦融入。大家好&#xff0c;在 API 测试的领域中&#xff0c;Postman 是一款极为强大且广泛使用的工具。其中&#xff0c;断言和日志调试功能扮演着至关重要的角色。 一、介绍 断言允许我们在测试过程中验证 API 的响应是否符合预期。通过设定各…

揭秘高效引流获客的艺术

在数字营销的海洋中&#xff0c;吸引潜在客户的注意力就像捕捉闪烁的鱼群——需要技巧、耐心和正确的工具。有效的引流获客策略能为企业带来生机&#xff0c;如同春风拂过荒漠&#xff0c;唤醒沉睡的种子。本文将带你领略那些让企业脱颖而出的获客秘籍&#xff0c;让你的目标客…

嫦娥六号揭秘真相:阿波罗登月是真是假?一文终结所有疑问!

近期&#xff0c;嫦娥六号的成功发射如同璀璨的星辰&#xff0c;再次将人们的视线聚焦于浩瀚的宇宙&#xff0c;与此同时&#xff0c;网络上关于美国阿波罗登月是否造假的争议也如潮水般涌现。一些声音宣称&#xff0c;嫦娥六号的发射为揭示美国阿波罗登月任务的真实性提供了关…

ubuntu server 22.04.4 系统安装详细教程

本教程使用vmware workstation 17创建虚拟机进行安装演示&#xff0c;安装方式和真机安装没有区别。 1、下载镜像 下载ubuntu server版本系统镜像&#xff0c;官网下载地址&#xff1a;https://cn.ubuntu.com/download/server/step1 注意&#xff1a;自己下载时需要确认是否是…

145.二叉树的后序遍历

刷算法题&#xff1a; 第一遍&#xff1a;1.看5分钟&#xff0c;没思路看题解 2.通过题解改进自己的解法&#xff0c;并且要写每行的注释以及自己的思路。 3.思考自己做到了题解的哪一步&#xff0c;下次怎么才能做对(总结方法) 4.整理到自己的自媒体平台。 5.再刷重复的类…

LeetCode/NowCoder-链表经典算法OJ练习2

最好的&#xff0c;不一定是最合适的&#xff1b;最合适的&#xff0c;才是真正最好的。&#x1f493;&#x1f493;&#x1f493; 目录 说在前面 题目一&#xff1a;分割链表 题目二&#xff1a;环形链表的约瑟夫问题 SUMUP结尾 说在前面 dear朋友们大家好&#xff01;&…

【JVM】从可达性分析,到JVM垃圾回收算法,再到垃圾收集器

《深入理解Java虚拟机》[1]中&#xff0c;有下面这么一段话&#xff1a; 在JVM的各个区域中&#xff0c;如虚拟机栈中&#xff0c;栈帧随着方法的进入和退出而有条不紊的执行者出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的&#xff08;尽管在…

数据可视化(十二):Pandas太阳黑子数据、图像处理——离散极值、核密度、拟合曲线、奇异值分解等高级操作

Tips&#xff1a;"分享是快乐的源泉&#x1f4a7;&#xff0c;在我的博客里&#xff0c;不仅有知识的海洋&#x1f30a;&#xff0c;还有满满的正能量加持&#x1f4aa;&#xff0c;快来和我一起分享这份快乐吧&#x1f60a;&#xff01; 喜欢我的博客的话&#xff0c;记得…

IDEA切换分支

方法一 1、选择要切换分支的module 2、右键&#xff0c;选择git 3、再点击branches 4、可以看到当前module的本地分支&#xff08;local Branches&#xff09;及远程分支&#xff08;Remote Branches&#xff09;列表。点击你要切换到的分支,Checkout即可。 方法二 1、点击…

vue3 第二十八节 (vue3 事件循环之JS事件循环)

1、什么是事件循环 事件循环就是消息队列&#xff0c;是浏览器渲染主线程的工作方式&#xff1b; 过去将消息队列&#xff0c;简单的分为宏任务 和微任务 两种队列&#xff0c;而对于现在复杂多变的浏览器环境&#xff0c;显然这种处理方式已经不能满足使用&#xff0c;取而代…

d17(154-168)-勇敢开始Java,咖啡拯救人生

目录 方法递归 字符集 编码-解码 IO流 字节流 字节输入流 InputSream FileInputStream 字节输出流 OutputSream FileOutputSream 释放资源的方式 try-catch-finallly try-with-resource 字符流 字符输入流 Reader FileReader 文件字符输出流 Writer FileWriter …

庙算兵棋推演AI开发初探(4-调用AI模型)

前面讲了如何开展编写规则脚本型Agent&#xff08;智能体&#xff09;的方法&#xff0c;现在探究一下如何调用知识型&#xff08;一般而言的训练出的模型&#xff09;智能体的方法。 这次调用的是庙算平台的demo&#xff08;网址见图&#xff09; 下载了“知识强化学习型”…