【是C++,不是C艹】 手把手带你实现Date类(附源码)

news2025/1/14 0:49:36

💞💞欢迎来到 Claffic 的博客💞💞

 👉 专栏:《是C++,不是C艹》👈

前言:

恍惚间,已经两个月没更新了 (;´д`)ゞ 我忏悔... 

但C++的学习不能停止!这期带大家实践一波,手把手教大家实现一个Date类,以感受C++类的魅力

注:

你最好是学完了C语言,并学过一些初阶的数据结构。


(没有目录) ヽ( ̄ω ̄( ̄ω ̄〃)ゝ 

Part1:一个引子

🌰我猜你用过倒数日:

🪄这其实是一种简单的日期机算器,用来计算某事件 已经 / 还有 多少天

那么我们搜索 日期计算器 会看到它的两大功能:

日期推算:

计算日期差:

 

❓那么这些功能是怎么实现的呢?

接下来就由我来带你揭开它的神秘面纱!

Part2:思路

1.日期推算

❓你想,先扔给你一个日期,再给你一个整数(正往后,负往前),你会怎么推算新日期?

简单情况:日相加,得到的日部分不超过当前月份的天数,就如 2023-8-21 与 1,得 2023-8-22 。

进位情况:日相加,得到的日部分超出当前月份的天数,给月进位,如 2023-8-21 与 12,得                            2023-9-2;

                  另有月满13,需要月重置1再给年进位,如 2023-8-21 与 133,得 2024-1-1

🚨注意还要考虑闰年 非闰年:闰年2月有29日  非闰年2月有28日。

2.计算日期差

❓再想,扔给你两个日期,你怎么计算两者之间间隔了多少天?

你是不是这样想的:年先做差,月再做差,日再做差,然后都换算成日,最后相加?

嗯,这是很直接的思路,可以,但没必要。

✅这里提供另一种思路

两个日期,必然有大有小(暂时忽略相等),找出较小者,让较小者往较大者增长,每次增加一日(++),加个计数器,计出来的就是两者的日期差。

或许你会感到麻烦,那你还是C语言的思维!

📢别忘了:C++是面向对象的语言!我们完全可以创建一个日期类来完成相关的计算和比较

Part3:实现

1.创建类

一个日期,包含年月日三个基本数据,这些不需要对用户公开,私有即可:

class Date
{
public:
    // todo

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

另外添加一些基本的方法

先来解决最基础的问题:那就是每个月有几天

我们不妨来封装一个方法,来获取这个月里有几天:

int Date::GetMonthDay(int year, int month) // 年份是来判断是否是闰年的
{
    // 第一个元素无实际效用,但就能保证 下标 == 月数
	static int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; // 一三五七八十腊...
	// 2月前提下再判断年的情况,减少消耗
    if (month == 2 && ((year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0))) 
		return 29;
	else
		return arr[month];
}

构造:

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

这里可以给一个全缺省,不给数据就默认是这个日期,还蛮香~

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

展示:

// 代码较短,在类中写就OK,不用跨文件了
void Print() const
{
	cout << _year << "-" << _month << "-" << _day << endl;
}

2.日期推算

⚔️我们期望得到这样的效果:

void DateTest1()
{
	Date d1(2023,8,21);
	Date d2 = d1 + 133;
	d1.Print();
	d2.Print();
}

👁️‍🗨️运行结果:

我们知道,普通的 + 是对整型,浮点型,字符型等内置类型起效的,而这里的 + 在 Date 和 int 之间起作用了,为甚?

没错,这就是C++大名鼎鼎的运算符重载!(其实 = 也重载了)

// 日期 + 天数
Date Date::operator+(int day)
{
	Date tmp(*this); // + 是不修改本身的!所以要先拷贝一份,最后返回的是拷贝修改后的内容

	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;
}
// 赋值运算符重载
Date& Date::operator=(const Date& d) // 原内容无需修改,加const
{
	_day = d._day;
	_month = d._month;
	_year = d._year;

	return *this;
}

3.计算日期差

⚔️预期效果:

void DateTest3()
{
	Date d5(2023, 8, 21);
	Date d6(2004, 3, 30);
	cout << d5 - d6 << endl;
	cout << d6 - d5 << endl;
}

👁️‍🗨️运行结果:

很明显,这是重载了 - ,使得 - 能在两个 Date 类之间起作用

🗡️按照二趴提供的思路,写下这样的代码:

int Date::operator-(const Date& d) const
{
	Date max = *this;
	Date min = d;
	int flag = 1; // 巧妙的flag,调节最后结果的正负
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}
	return n * flag;
}

🗡️我们发现,里面的 <, !=, ++ 都是需要针对 Date 进行重载的:

// <运算符重载
bool Date::operator<(const Date& d) const
{
	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;
	}

	return false;
}
// ==运算符重载
bool Date::operator==(const Date& d) const
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

// !=运算符重载
bool Date::operator != (const Date& d) const
{
	return !(*this == d);
}
// 前置++
Date& Date::operator++()
{
	*this += 1; // 还需重载 +=
	return *this;
}

// 日期+=天数
Date& Date::operator+=(int day) // += 需要修改本身
{
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}

经过一系列重载后,就可以达到计算日期差的效果咯!

Part4:其他功能

1.输入输出

❓在实现了两种主要的功能之后,既然把 Date 当作一个类了,那为甚马不能搞得像输入一个整数那样进行输入呢?

⚔️预期效果

void DateTest4()
{
	Date d7;

	cin >> d7;
	cout << d7 << endl;
}

👁️‍🗨️运行结果:

🗡️没错,这需要将 cin 和 cout 进行重载:

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;
}
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;

	return out;
}

但是这样还不行,因为 cin 修改了私有的数据,哪有什么办法能让这个重载能访问私有的数据呢?

对,那就是友元

可以在类中声明,这个重载是类的朋友,允许他访问私密~

friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);

2.前置和后置

❓我们知道,++ / -- 是有前置和后置之分的,那么在重载中前置和后置又是怎么区分的呢?

这里就以 ++ 为例吧:

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

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

前置:先计算,后使用

后置:先使用,后计算

实现中,后置事先拷贝了自身,返回的还是原来的值,做到了后计算;而前置直接修改自身,返回自身,做到了先计算;

传参中,后置用 int 来与前置重载做区分,语法这样规定;

返回值上,后置返回类型,前置返回引用。

源码在此

Date.h:

#pragma once
#include<iostream>
#include<cassert>
using namespace std;

class Date
{
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month);

	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	
	// 全缺省的构造函数
	Date(int year = 2008, int month = 1, int day = 1);

	// 赋值运算符重载
	Date& operator=(const Date& d);

	// 日期+=天数
	Date& operator+=(int day);

	// 日期+天数
	Date operator+(int day);

	// 日期-天数
	Date operator-(int day);

	// 日期-=天数
	Date& operator-=(int day);

	// 前置++
	Date& operator++();

	// 后置++
	Date operator++(int);

	// 后置--
	Date operator--(int);

	// 前置--
	Date& operator--();

	// >运算符重载
	bool operator>(const Date& d) const;

	// ==运算符重载
	bool operator==(const Date& d) const;

	// >=运算符重载
	bool operator >= (const Date& d) const;

	// <运算符重载
	bool operator < (const Date& d) const;

	// <=运算符重载
	bool operator <= (const Date& d) const;

	// !=运算符重载
	bool operator != (const Date& d) const;

	// 日期-日期 返回天数
	int operator-(const Date& d) const;

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

Date.cpp:

#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"

// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
	static int arr[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 arr[month];
}

// 全缺省的构造函数
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);
	}
}

// 赋值运算符重载
Date& Date::operator=(const Date& d)
{
	_day = d._day;
	_month = d._month;
	_year = d._year;

	return *this;
}

// <运算符重载
bool Date::operator<(const Date& d) const
{
	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;
	}

	return false;
}

// ==运算符重载
bool Date::operator==(const Date& d) const
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

// >=运算符重载 d1 >= d2
bool Date::operator >= (const Date& d) const
{
	return !(*this < d);
}

// >运算符重载
bool Date::operator > (const Date& d) const
{
	return !(*this <= d);
}

// <=运算符重载
bool Date::operator <= (const Date& d) const
{
	return *this < d || *this == d;
}

// !=运算符重载
bool Date::operator != (const Date& d) const
{
	return !(*this == d);
}

// 日期+=天数
Date& Date::operator+=(int 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)
{
	Date tmp(*this);

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

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


// 日期-天数
Date Date::operator-(int day)
{
	Date tmp = *this;

	tmp._day -= day;
	while (tmp._day <= 0)
	{
		tmp._day += GetMonthDay(tmp._year, tmp._month);
		tmp._month--;
		if (tmp._month == 0)
		{
			tmp._year--;
			tmp._month = 12;
		}
	}
	return tmp;
} 

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

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

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


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

// 日期-日期 返回天数
int Date::operator-(const Date& d) const
{
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (*this < d) // C艹,多了个分号 -- bug 记录
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}
	return n * flag;
}

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

 

代码已上传至 我的 gitee

拿走不谢~


总结: 

实现 Date 类,并没有那么难,明确类的特征,捕捉到必要数据,再进行方法的实现即可,这次用了不少运算符重载。

码文不易 

如果你觉得这篇文章还不错并且对你有帮助,不妨支持一波哦  💗💗💗

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

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

相关文章

K8s+Docker+KubeSphere+DevOps笔记

K8sDockerKubeSphereDevOps 前言一、阿里云服务器开通二、docker基本概念1.一次构建、到处运行2、docker基础命令操作3、docker进阶操作1.部署redis中间件2.打包docker镜像 三、kubernetes 大规模容器编排系统1、基础概念&#xff1a;1、服务发现和负载均衡2、存储编排3、自动部…

基于springboot+vue的流动人口登记系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

因果推断(五)基于谷歌框架Causal Impact的因果推断

因果推断&#xff08;五&#xff09;基于谷歌框架Causal Impact的因果推断 除了传统的因果推断外&#xff0c;还有一些机器学习框架可以使用&#xff0c;本文介绍来自谷歌框架的Causal Impact。该方法基于合成控制法的原理&#xff0c;利用多个对照组数据来构建贝叶斯结构时间…

javaScript:常用的js字符串方法

目录 一.前言 二.字符串方法 1.charAt(num) 获取字符串指定位置上的字符 解释 示例 注意 2.length属性 获取字符串长度 解释 示例讲解 3.substring()字符串的截取 解释 特点 示例 4.slice()字符串截取 解释 特点 示例 应用 单行文本加省略号 字符串劫…

Dockerfile制作镜像与搭建LAMP环境

1、编写Dockerfile制作Web应用系统nginx镜像&#xff0c;生成镜像nginx:v1.1&#xff0c;并推送其到私有仓库。 具体要求如下&#xff1a; &#xff08;1&#xff09;基于centos基础镜像&#xff1b; &#xff08;2&#xff09;指定作者信息&#xff1b; &#xff08;3&#x…

卷积神经网络——中篇【深度学习】【PyTorch】

文章目录 5、卷积神经网络5.5、经典卷积神经网络&#xff08;LeNet&#xff09;5.5.1、理论部分5.5.2、代码实现 5.6、深度卷积神经网络&#xff08;AlexNet&#xff09;5.6.1、理论部分5.6.2、代码实现 5.7、使用块的网络&#xff08;VGG&#xff09;5.7.1、理论部分5.7.2、代…

从浅到深研究矩阵的特征值、特征向量

本篇特征值、特征向量笔记来源于MIT线性代数课程。 矩阵特征值与特征向量 ✨引言✨什么是特征向量呢&#xff1f;✨表示✨从特例看特征值与特征向量✨如何求解方程▶️ 思路&#xff1a;✨对称矩阵例子&#xff1a;✨对比观察两个矩阵及它们的特征值及特征向量&#xff1a;✨旋…

C语言小白急救 整型与浮点型在内存中的存储(理论知识+代码示例)

文章目录 一、有无符号整型的存储1.整形家族2.整形在内存中的存储3.大小端介绍4.signed 与 unsigned 类型存储例子&#xff1a;1.2.3.4.5. 二、浮点型的存储1.浮点型家族2.浮点型的存储例子&#xff1a; 一、有无符号整型的存储 1.整形家族 字符在内存中存储的是字符的ASCII码…

Django视图-HttpRequest请求对象和HttpResponse响应对象

文章目录 HttpRequestHttpResponse实践request对象的属性和方法响应 def index(request): 这个request其实就是内部已经封装好的Http请求HttpRequest&#xff0c;它是一个请求对象Django中的视图主要用来接受Web请求&#xff0c;并做出响应。 视图的本质就是一个Python中的函数…

解决Windows下的docker desktop无法启动问题

以管理员权限运行cmd 报错&#xff1a; docker: error during connect: Post http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.40/containers/create: open //./pipe/docker_engine: The system cannot find the file specified. In the default daemon configuration on Windows,…

【image captioning】自用数据集BUTD特征提取流程

自用数据集BUTD特征提取流程 作者:安静到无声 个人主页 目录 自用数据集BUTD特征提取流程源数据生成推荐专栏源数据生成 基于程序bottom-up-attention.pytorch可以提取图片的BUTD特征,具体方法详见:MILVLG/bottom-up-attention.pytorch: A PyTorch reimplementation of bo…

2023年国赛数学建模思路 - 案例:粒子群算法

文章目录 1 什么是粒子群算法&#xff1f;2 举个例子3 还是一个例子算法流程算法实现建模资料 # 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 什么是粒子群算法&#xff1f; 粒子群算法&#xff08;Pa…

航空电子设备中的TSN通讯架构—直升机

前言 以太网正在迅速取代传统网络&#xff0c;成为航空电子设备和任务系统的核心高速网络。本文提出了以太网时间敏感网络(TSN)在航空电子设备上应用的技术优势问题。在实际应用中&#xff0c;TSN已成为一个具有丰富的机制和协议的工具箱&#xff0c;可满足与时间和可靠性相关…

第九课 过去分词、现在分词作非谓语

文章目录 前言一、过去分词的定义1、及物动词的过去分词2、双宾动词的过去分词 二、过去分词和过去分词短语的作用1、过去分词做前置定语过去分词构成的形容词有被动或者完成意义&#xff0c;如果单独的一个过去分词不及物只有完成意义&#xff0c;如果是一个及物动词的过去分词…

索引构造与信息检索:让 ChatGPT 成为 Selenium 问答助手

这是chatgpt为我生成的3个标题&#xff0c;我选了第3个。 利用 Langchain 和 GPT 实现 Selenium 机器人自动问答 向量化存储和检索&#xff1a;如何用相似度搜索匹配 Selenium 知识&#xff1f; 索引构造与信息检索&#xff1a;让 ChatGPT 成为 Selenium 问答助手 之前有很…

数据结构 - 线性表的顺序存储

一、顺序存储定义&#xff1a; 把逻辑上相邻的数据元素存储在物理上相邻的存储单元中。简言之&#xff0c;逻辑上相邻&#xff0c;物理上也相邻顺序表中&#xff0c;任一元素可以随机存取&#xff08;优点&#xff09; 二、顺序表中元素存储位置的计算 三、顺序表在算法中的实…

bootstrap-modal调用ajax后不经过回调函数

说明&#xff1a;我用的是boostrap的弹框&#xff0c;表单用的是layui的&#xff0c;个人觉得bootstrap比layui的弹框好看点&#xff0c;能自适应高度。 如图&#xff1a;点击保存后里面的内容不执行 原因&#xff1a;type用的是submit 解决&#xff1a;把submit改为button

数智赋能共筑未来,聚好看DBdoctor亮相中国数据库技术大会

8月16日—18日,第14届中国数据库技术大会(DTCC-2023)在北京国际会议中心举行。作为国内数据库领域规模最大的技术交流盛会,吸引了众多业内知名企业和数百名行业专家参加。聚好看携新内核级数据库性能洞察新产品DBdoctor亮相,吸引来自数据库管理专业人士关注。 数据库性能问题诊…

上位机一般的开发工具有哪些?

上位机开发工具是用于开发和构建上位机应用程序的软件工具。它们提供了一系列功能和资源&#xff0c;帮助开发人员设计、编写和调试上位机应用程序。以下是一些常见的上位机开发工具&#xff1a;Visual Studio&#xff1a;作为一种集成开发环境&#xff08;IDE&#xff09;&…

车辆PEPS无钥匙方案

汽车无钥匙系统&#xff08;PEPS无钥匙方案&#xff09;作为车辆智能化变革下的一项创新技术&#xff0c;正在被广泛应用于各种车型中。PEPS无钥匙方案主要具有三项功能&#xff0c;即PKE&#xff08;Passive Keyless Entry&#xff09;被动式无钥匙进入、RKE&#xff08;Remot…