C++实现日期类

news2025/7/19 7:25:44

文章目录

  • 前言
  • 1.日期类的功能分析
    • 1.大致分析
    • 2.接口设计
  • 2.具体实现
    • 1.日期类的成员函数和成员变量
    • 2.初始化(构造函数)
    • 3.对日期进行天数推算
    • 4.比较相关的运算符重载
    • 5.前置后置自增或自减
    • 6.日期相减与流插入流提取
      • 1.日期相减
      • 2.重载流插入和流提取
  • 3.总结

前言

之前介绍了C++面向对象的初阶知识,为了更理解相关知识,加深印象,我们可以先试着写个日期类来巩固所学。


1.日期类的功能分析

1.大致分析

我们写日期类之前可以先去百度看看一些简蛋的日期转化工具的大致功能,从中提炼出我们自己日期类的功能。我大致将日期类分为以下功能:天数转化日期(给定一日期往前推算日期或往后推算日期),两个日期减相差多少天,为了用到运算符重载的知识可以进行日期的判断比较,已经针对输入输出实现输入输出的运算符重载。


2.接口设计

日期类构造函数这里就不算在内了,同时日期类也不设计资源申请,这里就不提析构函数的实现了,这里主要是围绕日期类功能的相关接口。
打印函数 void Print();
得到月份的天数 int GetMonthDay(int year, int month);
日期+=天数 Date& operator+=(int day);
日期+天数 Date operator+(int day);
日期-天数 Date operator-(int day);
日期-=天数 Date& operator-=(int day);
后置++ Date operator++(int);
后置-- Date operator--(int);
前置++ Date& operator++();
前置-- Date& operator--();
赋值重载 Date& operator=(const Date& d);
>运算符重载 bool operator>(const Date& d);
==运算符重载 bool operator==(const Date& d);
>=运算符重载 bool operator >= (const Date& d);
<运算符重载bool operator < (const Date& d);
<=运算符重载 bool operator <= (const Date& d);
!=运算符重载 bool operator != (const Date& d);
日期-日期 返回天数 int operator-(const Date& d);
流插入 friend ostream& operator<< (ostream& out, const Date& d);
流提取friend istream& operator>>(istream& in, Date& d);

日期类的成员这都很好确定,大致就3个:year month day.

2.具体实现

1.日期类的成员函数和成员变量

class Date
{
public:
    //打印函数
    void Print();
    //得到月份的天数
    int GetMonthDay(int year, int month);
    //全缺省构造函数
	Date(int year=1, int month=1, int day=1);
    //拷贝构造
	Date(const Date &d);
    // 析构函数
    ~Date()
    {
        ;
    }
    // 日期+=天数
    Date& operator+=(int day);
    // 日期+天数
    Date operator+(int day);
    // 日期-天数
    Date operator-(int day);
    // 日期-=天数
    Date& operator-=(int day);
    // 后置++
    Date operator++(int);
    // 后置--
    Date operator--(int);
    //前置++
    Date& operator++();
    // 前置--
    Date& operator--();
    //赋值重载
    Date& operator=(const Date& d);
    // >运算符重载
    bool operator>(const Date& d);
    // ==运算符重载
    bool operator==(const Date& d);
    // >=运算符重载
    bool operator >= (const Date& d);

    // <运算符重载
    bool operator < (const Date& d);
    // <=运算符重载
    bool operator <= (const Date& d);
    // !=运算符重载
    bool operator != (const Date& d);
    // 日期-日期 返回天数
    int operator-(const Date& d);
    //流插入
    friend ostream&  operator<< (ostream& out, const Date& d);
    //流提取
    friend istream& operator>>(istream& in, Date& d);
    private:
    int _year;
    int _month;
    int _day;
};


其实这个日期类成员全凭借个人意愿,有些功能如果没想好,可以边写边想,或者后序加上也行。不过最好还是写代码之前提前想好,这样就可以专心写代码实现功能了。


2.初始化(构造函数)

之前我们知道构造函数就是用来初始化的,这里我就实现了两个构造,一个直接构造,另一个是拷贝构造。这里要注意一点对于用户输入的日期要进行判断,不能使用非法的值用来构造。其实这里的拷贝构造我们不现实也可,可以使用默认生成的,因为日期类不涉及一些资源的申请。

Date::Date(int year=1,int month=1,int day=1)
{
	if(month > 0 && month < 13&& 
	(day > 0 && day<=GetMonthDay(year, month)))
	{    
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "非法初始化" << endl;
	}

}

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

我们可以先写个Getmonthday函数用来获取每一年每个月的天数,这里要注意对闰年的判断。

int Date:: GetMonthDay(int year, int month)
{
    int MonthDay[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 MonthDay[month];
    }
}

这里只用对2月进行单独处理即可。当然也可以加入一些对输入年份和月份判断限制。我这里就没有那么严谨进行限制判断。


3.对日期进行天数推算

这里就需要用到了运算符重载的知识了,天数推算就是日期类对象进行天数的相加或者相减。为了支持这种相减相加的操作,只能对+ -运算符进行重载了。

关于日期天数加键的逻辑分析
在这里插入图片描述

+= 与-=

Date& Date::operator+=(int day)
{  //复用-=运算符重载
	if (day < 0)
	{
		*this -= -day;
    }
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}

Date& Date:: operator-=(int day)
{  //复用+=运算符重载
	if (day < 0)
	{
		*this +=-day;
	}
	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			_month = 12;
			_year--;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

这里使用引用返回是为了更加符合运算符特性,比如:b=(a+=c),如果没有返回值的话,出现例子种的场景显然是有问题的。代码中还涉及复用的技巧,对于为负值的天数相加时,可以复用相减的运算符重载,就是减去负day。处理相减时也可以这样处理,如果day是负数,就是加上负day。


+ 与-

Date Date::operator+(int day)
{   //复用+= 
	//tem是临时变量所以不用引用返回
	Date tem = *this;
	tem += day;
	return tem;
}

Date Date::operator-(int day)
{
	Date tem = *this;
	tem += day;
	return tem;
}

这里要提一点 a+=1 a的值肯定是改变了, a+1 这个a的值肯定是没有改变的。基于此实现- +运算符重载的时候,先创建一个临时变量,为了不该变原来对象中的数据。因为是在函数体内创建的临时变量实用这里就使用的传值返回。当然这个返回值的也是为了符合运算符的特性:比如a=b-1因为之前实现了+=和-=这里对临时变量直接复用即可。


这里补充一个运算符重载=赋值运算符重载

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

这里实现的时候多加了一个判断自己对自己赋值的时候不用处理,减少不必要的程序系统开销。为了支持运算符连续赋值的特性,这里采用的也是引用返回。


4.比较相关的运算符重载

之前我们实现- +运算符重载的时候使用了代码复用,这里也可以代码复用,我们可以先实现两个运算符重载,其他的都可以复用,我选取的<和==,其他都是复用这两个。

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

bool Date::operator<(const Date& d)
{
	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)
{ //复用
	return *this < d || *this == d;
}

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

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

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

当我们使用了<和==之后,如果两个日期类对象,是小于或者等于就实现了<=,如果不是<=就是大于,如果大于或者等于就实现了>=,如果不是 == 就是!=,这样一来代码的复用就实现了。代码复用算是一个编程小技巧,尽可能的多用。


5.前置后置自增或自减

前置++ 我们知道改变自己,后置++不改变自己。同时为了保持原本运算符的连续性,运算符重载是需要有返回值的,这点上面就提到过,返回值类型根据运算符特性特性来决定,如果是改变自己采用引用返回即可,其他情况就采用采用传值返回。

前置++ 代码示例

Date& Date::operator++()
{
	//复用
	*this += 1;
	return *this;
}

后置++ 代码示例

//后置++
Date Date::operator++(int)
{
	Date tmp(*this);
	tmp+= 1;
	return tmp;
}

这里要提一下,C++中为了区分前置++和后置++ ,后置++多了一个int 参数 主要是为了构成函数重载,用于区分这两个运算符重载,实际使用的时候不用传参。同理,后置--的时候也需要这样处理。


前置-- 代码示例

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

后置-- 代码示例

//后置--
Date Date::operator--(int)
{
	Date tem(*this);
	tem -= 1;
	return tem;
}

当然这里实现的时候也是代码复用


6.日期相减与流插入流提取

1.日期相减

日期相减我们可以引入一个计数变量用于计算两个日期的差值,我们先选出较小的日期进行前置++直到和较大的日期相等为止,每自增一次就计数一次。这样就可以很好的计算出日期差值。

代码示例

int Date::operator-(const Date& d)
{
	Date max(*this);
	Date min(d);
	int flag = 1;
	if (max < min)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	//复用!=
	int day = 0;
	while (max != min)
	{
		++min;
		++day;
	}
	return flag * day;
}

可能前者的日期比后者的大,所以引入一个变量flag标记一下,用于确定结果的正负值。这里还是涉及到代码的复用。


2.重载流插入和流提取

我们刚接触c++的时候可能觉得cout和cin很神奇能够自动识别数据类型,通过之前的学习我们不难猜出cont和cin是使用了运算符重载和函数重载能够自动识别数据类型。我们可以去c++官网查一下cout和cin.

在这里插入图片描述

我们不难看出cin 是iostrem头文件中的一个对象,它的类型是istream,cout也是iostrem头文件中的一个对象,它的类型是ostream。因为c++的官网库中对内置类型进行函数重载和运算符重载了,但是没有对日期类进行重载,我们日期类无法直接cout << Date 或者cin>> Date ,我们不能动官方库,只能直接对是>>进行重载,如果将其封装成Date类的成员函数就会发生如下问题:

在这里插入图片描述


这样写用倒是能用 但是过于奇怪了。为了更加和库中的使用方法保持一致,我们将这个函数写成全局函数,写成全局函数后就有个问题不能访问类中的成员,为了能够访问类中成员我们必须得使用friend将这个全局函数设置成类中的友元函数来处理。同理,cin也是这样处理。

//为了能沟连续输出或输入采用引用作为返回值
 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;
 }

3.总结

日期类实现难点在于理清加减天数的逻辑,这点是比较重要的。理清后后面的代码就很好写了。为了得到每个月的天数这个功能是频繁使用的,我们可以将其封装成一个函数,这样代码逻辑更加清晰。

编程技巧:学会代码的复用,这样可以使得代码更加简洁,同时对于一些不用改变成员变量的函数形参可以使用const修饰,尽可能使用const是个好习惯。比如上述的日期比较函数不仅可以使用const修饰还可以让其成为const成员函数,专门用于修饰*this。因为这种只需要读权限即可,const 对象也可以调用该成员函数。

以上内容如有问题,欢迎指正,谢谢!

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

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

相关文章

数据结构与算法—链表list

目录 链表 链表类型 链表插入 链表删除 写程序注意点 与数组区别 链表应用 LRU 实现思想 链表 链表&#xff0c;一种提高数据读取性能的技术&#xff0c;在硬件设计、软件开发中有广泛应用。常见CPU缓存&#xff0c;数据库缓存&#xff0c;浏览器缓存等。缓存满时&#…

mongoDB5以上实现单机事务

原理就是因为目前mongodb只有副本&#xff0c;分片支持事务。我们就让单机变成单节点副本&#xff0c;同时又是主节点&#xff0c;可以读写一、修改bin目录下的mongod.cfg新增配置replication:replSetName: rs0二、重启mongodb服务、初始化&#xff08;这是windows下的启动命令…

2月24日作业

题目&#xff1a;通过操作Cortex-A7核&#xff0c;串口输入相应的命令&#xff0c;控制LED灯进行工作--->上传CSDN 1.例如在串口输入led1on,开饭led1灯点亮 2.例如在串口输入led1off,开饭led1灯熄灭 3.例如在串口输入led2on,开饭led2灯点亮 4.例如在串口输入led2off,开饭led…

day20_Map

今日内容 上课同步视频:CuteN饕餮的个人空间_哔哩哔哩_bilibili 同步笔记沐沐霸的博客_CSDN博客-Java2301 零、 复习昨日 一、作业 二、比较器排序 三、Collections 四、Map 五、HashMap 六、TreeMap 零、 复习昨日 HashSet 不允许重复元素,无序 HashSet去重原理: 先比较hashco…

Java知识复习(二)Java集合

1、List、Set和Map的区别 List&#xff1a;存储的顺序是有序的、可重复的Set&#xff1a;存储的顺序是无序的、不可重复的Map&#xff1a;使用键值对存储&#xff0c;Key和Value都是无序的&#xff0c;其中Key不可重复&#xff0c;而Value可重复 2、ArrayList和LinkedList的区…

node报错

记录bug:运行 npx -p storybook/cli sb init 时报错gyp info spawn C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exegyp info spawn args [gyp info spawn args build/binding.sln,gyp info spawn args /nologo,gyp info spawn args…

OpenCV只含基本图像模块编译

编译OpenCV4.5.5只含基本图像模块&#xff0c;环境为Windows10 x64CMake3.23.3VS2019。默认编译选项编译得到的OpenCV库往往大几百MB甚至上GB&#xff0c;本文配置下编译得到的库压缩后得到的zip包大小仅6.25MB&#xff0c;适合使用OpenCV基本图像功能模块的项目移植而不牵涉其…

电子技术——伯德图与反馈

电子技术——伯德图与反馈 增益和相位边距 从上两节我们知道环路增益 AβA\betaAβ 可以决定一个系统的稳定性&#xff0c;一个更加简单和有效的方法是我们可以绘制 AβA\betaAβ 的伯德图&#xff08;因为相位可以达到360度&#xff0c;因此这是一个四阶响应系统&#xff09;…

行锁、表锁、主键外键、表之间的关联关系

Java知识点总结&#xff1a;想看的可以从这里进入 目录2.4、行锁、表锁2.5、主键、外键2.5.1、主键2.5.2、外键2.6、表的关联关系2.4、行锁、表锁 MyISAM默认采用表级锁&#xff0c;InnoDB默认采用行级锁。 表锁&#xff1a;开销小&#xff0c;加锁快&#xff0c;不会出现死锁…

KTV「消亡史」:辉煌、挫折与新生

【潮汐商业评论/原创】这是Ina工作的第五年&#xff0c;疫情之后&#xff0c;第一场大学同学聚会就定在了周末。同学群里大家热烈地讨论着聚会的地点&#xff0c;“要不咱们去KTV吧&#xff0c;哈哈哈哈哈哈”&#xff0c;突然有人提议到。“谁还去KTV啊”“多没意思啊”……随…

按字典序排列的最小的等价字符串[拆解并查集]

并查集前言一、按字典序排列的最小的等价字符串二、并查集总结参考文献前言 并查集有什么用&#xff1f;并查集是什么&#xff1f;搞懂这两个问题&#xff0c;相关的并查集问题就变得非常easy&#xff01; 一、按字典序排列的最小的等价字符串 二、并查集 有一种方法&#x…

工单模型的理解与应用

工单&#xff08;任务单&#xff09;模型的定义 工单模型是一种分派任务的方法&#xff0c;可以用来跟踪、评估和报告任务的完成情况。它通常用于针对特定目标的重复性任务或项目&#xff0c;以确保任务能够按时完成并符合期望的标准。   工单模型的基本流程为&#xff1a;提…

GroupDocs.Merger for Java

GroupDocs.Merger for Java GroupDocs.Merger for Java是一个文档操作API&#xff0c;可帮助您合并、拆分、交换或删除文档页面。API通过启用或禁用密码提供保护&#xff0c;并允许开发人员加入PDF、Microsoft Word、Excel和Powerpoint文档。 支持的文件格式 Microsoft Office格…

常见激活函数Activation Function的选择

Activation Function激活函数一般会神经网络中隐层和输出层上&#xff0c;其中作用在输出层主要用于适配输出&#xff0c;比如sigmoid函数可用于生成[0,1]之间的概率估计值。而作用于隐层主要用于增加神经网络的非线性&#xff0c;增加了网络的表达能力&#xff0c;本文主要介绍…

测试员拿到新项目怎么着手测试?不要慌,照做准没错

一、目标 结合公司现有的项目情况制定合理规范的测试流程&#xff0c;提高测试效率和产品质量&#xff0c;尽可能减少客户对产品的问题反馈&#xff0c; 核心还是要加强项目组成员之间的工作交流和沟通&#xff0c;保证整个项目的高效率的按质按量的交付。 二、测试流程说明…

【git】git介绍与安装

Git是什么 Git是目前世界上最先进的分布式版本控制系统 git是由linux的创始人用c语言写的 和集中式比较 历史记录&#xff1a;Git 更加轻量级&#xff0c;每次提交只记录变化&#xff0c;而 SVN 每次提交会存储完整的文件&#xff1b; 版本管理&#xff1a;Git 更加灵活&…

Windows10 下测试 Intel SGX 功能

文章目录参考文献系统要求一、安装Open Enclave SDK 环境&#xff08;一&#xff09;什么是Open Enclave SDK&#xff08;二&#xff09;启动SGX功能方法一&#xff1a; BIOS启动方法二&#xff1a;软件方式启动&#xff08;三&#xff09;安装必要环境&#xff08;1&#xff0…

【微信小程序】-- 其它常用组件介绍 -- button image(八)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#…

cmake 入门二 库的编译,安装与使用

工程描述 &#xff11;&#xff0c;建立一个静态库和动态库&#xff0c;提供HelloFunc 函数供其他程序编程使用&#xff0c;HelloFunc 向终端输出Hello World字符串。 &#xff12;&#xff0c;安装头文件与共享库。 1 库的工程结构 1.1 工程目录下的CMakeLists.txt PROJECT…

【回眸】记录英飞凌TC397开发项目功能测试流程(包含CAN通信配置)

前言 记录一下从拿到新板子到最终测试的流程 过程 1、包线和接线帽 将大板子的电源接好&#xff0c;用不到的铜插头用美纹纸&#xff08;胶带&#xff09;包起来&#xff0c;防止线束之间相互碰撞导致短路&#xff0c;第一次下载需要把新板子用接线帽来接好&#xff0c;因为…