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.mzph.cn/web/11494.shtml

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

相关文章

element ui的确认提示框文字样式修改

修改确认提示框文字样式修改&#xff0c;使用message属性修改&#xff1a; 例&#xff1a; js代码&#xff1a; this.$msgbox({title: 确定要删除吗?,message: this.$createElement(p, null, [this.$createElement(span, { style: color: red }, 该素材一旦删除&#xff0c;…

Spring Boot日志

目录 一、日志概述 1、为什么要学习日志&#xff1f; 2、日志的用途 &#xff08;1&#xff09;系统监控 &#xff08;2&#xff09;数据采集 &#xff08;3&#xff09;日志审计 二、日志使用 1、打印日志 &#xff08;1&#xff09;在程序中得到日志对象 &#xf…

CentOs搭建Kubernetes集群

kubeadm minikube 还是太“迷你”了&#xff0c;方便的同时也隐藏了很多细节&#xff0c;离真正生产环境里的计算集群有一些差距&#xff0c;毕竟许多需求、任务只有在多节点的大集群里才能够遇到&#xff0c;相比起来&#xff0c;minikube 真的只能算是一个“玩具”。 Kuber…

物联网五层架构分析

物联网五层架构分析 随着科技的迅速发展&#xff0c;物联网&#xff08;IoT&#xff09;作为日常生活中不可或缺的一部分&#xff0c;已融入人们的生活和工作中。物联网五层架构&#xff0c;包括感知层、网络层、数据层、应用层和业务层&#xff0c;扮演着关键的角色。 感知层 …

网络库-libcurl介绍

1.简介 libcurl 是一个功能强大的库&#xff0c;支持多种协议&#xff0c;用于数据传输。它广泛应用于实现网络操作&#xff0c;如HTTP、HTTPS、FTP、FTPS、SCP、SFTP等。libcurl 提供了丰富的 API&#xff0c;可以在多种编程语言中使用。 libcurl 主要特点 支持多种协议&am…

FreeRTOS计数型信号量

目录 一、计数型信号量简介 二、计数型信号量相关API 1、创建计数型信号量 2、释放计数型信号量 3、获取计数型信号量 4、获取计数型信号量的计数值 三、计数型信号量实操 1、实验需求 2、CubeMX配置 3、代码实现 一、计数型信号量简介 ①取值只有0与1两种状态的信号…

基于Springboot的滴答拍摄影

基于SpringbootVue的滴答拍摄影设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 摄影作品 摄影服务 摄影论坛 后台登录 后台首页 用户管理 摄影师管理 摄影作…

YOLOv8小白中的小白安装环境教程!没一个字废话,看一遍不踩坑!

文章目录 去哪里下代码&#xff1f;怎么下代码&#xff1f;怎么装环境&#xff1f;命令行界面(CLI)指令和Python脚本区别&#xff1f;附录1 conda常用指令附录2 git常用指令附录3 项目代码文件作用 去哪里下代码&#xff1f; 下载代码请大家直接去 YOLOv8的官方仓库下载&#…

让 计算机 将 数学 公式 表达式 的计算过程绘制出来 【mathematical-expression(MAE)】

目录 文章目录 目录介绍开始实战引入数学表达式计算库引入流程图代码生成库开始进行生成 介绍 大家好 今天我们来分享一个新知识&#xff0c;将数学表达式的整个计算过程&#xff0c;以及计算繁多结果在 Java 中绘制出来&#xff0c;计算机中的数学表达式计算的功能很常见了&a…

区块链的跨链交互:从学校间交流看跨链技术

区块链是一种去中心化的分布式账本技术&#xff0c;它通过加密学和共识机制来确保数据的安全性和不可篡改性。每个区块链就像一所独立的学校&#xff0c;有自己的制度、学生和重点专业。它们各自运行&#xff0c;有时在同一领域展开不同的活动。随着区块链技术的不断发展&#…

学习笔记:Adaptive Platform(AP)适配到RTOS

一、背景 1、AP版本 Adaptive Platform AUTOSAR R20-11版本标准支持C14。CM模块支持DDS、SOME/IP协议 2、RTOS RTOS-A核&#xff0c;当前完全支持POSIX PSE51、POSIX PSE52接口&#xff0c;POSIX PSE53部分支持&#xff0c;POSIX PSE54基本不支持。详细接口参考&#xff1a…

第十四天:PHP 开发,输入输出类留言板访问 IPUA 头来源

1.PHP-全局变量$_SERVER 2.MYSQL-插入语法INSERT 3.输入输出-XSS&反射&存储 4.安全问题-XSS跨站&CSRF等 1.输入输出类安全问题 反射性xss 这个先准备一个数据&#xff0c;随便弄一个表名字&#xff0c;在随便弄一点数据存入即可 作为连接的数据库&#xff0c…

排序-归并排序(merge sort)

归并排序&#xff08;Merge Sort&#xff09;是一种分而治之的算法&#xff0c;它将原始数组分成越来越小的子数组&#xff0c;直到每个子数组只有一个元素&#xff0c;然后将这些子数组两两合并&#xff0c;过程中保持排序状态&#xff0c;最终合并成一个完全有序的数组。归并…

《一》Word文字编辑软件---架构设计分析

1&#xff0c;简单介绍 今天&#xff0c;我们来模拟offic软件中的word文档&#xff0c;运行如图&#xff1a; 运行程序后会出现主界面&#xff0c;顶端的菜单栏包括“文件”“编辑”“格式”“窗口”和“帮助五个主菜单。 菜单栏下面是工具栏&#xff0c;包含了系统常用的功能按…

如何判断海外住宅ip的好坏?

在海外IP代理中&#xff0c;住宅IP属于相对较好的资源&#xff0c;无论是用于工作、学习、还是娱乐&#xff0c;都能得到较好的使用效果。作为用户&#xff0c;该如何判断海外住宅IP的好坏呢&#xff1f; 稳定性与可靠性&#xff1a;海外住宅IP相比动态IP地址&#xff0c;通常具…

Java全局异常处理,@ControllerAdvice异常拦截原理解析【简单易懂】

https://www.bilibili.com/video/BV1sS411c7Mo 文章目录 一、全局异常处理器的类型1-1、实现方式一1-2、实现方式二 二、全局异常拦截点2-1、入口2-2、全局异常拦截器是如何注入到 DispatcherServlet 的 三、ControllerAdvice 如何解析、执行3-1、解析3-2、执行 四、其它4-1、设…

电脑提示找不到ffmpeg.dll无法继续执行代码怎么办?

电脑提示找不到找不到ffmpeg.dll无法继续执行代码怎么办&#xff0c;有什么好的解决办法&#xff0c;出现这样的弹出就会导致软件无法打开或者是异常关闭&#xff0c;找不到dll文件&#xff0c;是一个非常重要的电脑使用问题&#xff0c;会给使用者带来许多的麻烦。那么找不到d…

LeetCode746:使用最小花费爬楼梯

题目描述 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 请你计算并返回达到楼梯顶部的最低花费。 代码 …

MongoDB和AI 赋能行业应用:制造业和汽车行业

欢迎阅读“MongoDB和AI 赋能行业应用”系列的第一篇。 本系列重点介绍AI应用于不同行业的关键用例&#xff0c;涵盖制造业和汽车行业、金融服务、零售、电信和媒体、保险以及医疗保健行业。 随着人工智能&#xff08;AI&#xff09;在制造业和汽车行业的集成&#xff0c;传统…