当前位置: 首页 > news >正文

C++ —— 实现一个日期类

目录

一. 对日期类的介绍

二. 实现日期类

1. 运算符重载

2.日期类实现代码


一. 对日期类的介绍

通过对类和对象(这里链接是类和对象的介绍)的学习,类就是一种模型一样的东西,是对象一种抽象的描述。所以实现日期类,就是实现一个日期模型,里面有所对应的成员变量(属性:年/月/日),还有一些它的方法(成员函数:打印该日期、对日期加减等)。

二. 实现日期类

在实现日期类之前,我们还需要了解一下运算符重载,这是为了后续我们对日期的加、减天数以及日期减日期等做准备,因为当运算符被用于类类型的对象时,需要我们去重载运算符去指定新的含义。(C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。

//内置类型
int i = 1;
int j = 2;
int sum = 0;i = i + 1;
sum = i - j;//自定义类型对象若想用类似上面的就需要在类里面声明定义对应的运算符重载函数

1. 运算符重载

C++规定类类型对象使用运算符时,必须转换成对应运算符重载,若没有对应的运算符重载,则会编译报错。

也就是运算符被用于类类型的对象时,可以通过运算符重载的形式指定新的含义。

运算符重载是一个函数,它的名字是由关键字operator和运算符组成,也具有返回类型、参数列表、函数体。

class A
{
public:A(int a = 1, int b = 1):_a(a),_b(b){}//这里就是赋值运算符重载A& operator=(const A& a){_a = a._a;_b = a._b;return *this;}
private:int _a;int _b;
};int main()
{A a1;A a2(5, 2);a1 = a2;return 0;
}

我们来查看验证是否按照指定新的含义进行:

赋值之前:

赋值之后:

 可以看到a1里的成员确实被a2里的成员变量赋值了。

注意:

//上述代码中a1 = a2;
//其实详细写法是:
a1.operator=(a2);//但它们俩意义相同都是为了调用赋值运算符重载

一元运算符只有一个参数,二元运算符要有两个参数。(注意:二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数)

也就是说,重载运算符函数的参数个数和该运算符作用的运算对象数量一致的。

上面我们看到A类重载=运算符只写了一个函数参数,这是因为它是成员函数,第一个参数默认传了this指针(类和对象有介绍)。

注意:重载运算符必须至少有一个类类型的形参,不能通过运算符重载改变内置类型对象的含义。

若不是成员函数,就按照上面规则来:

class A
{
public:A(int a = 1, int b = 1):_a(a),_b(b){}A& operator=(const A& a){_a = a._a;_b = a._b;return *this;}int _a;int _b;
};//注意:operator + 必须至少有一个类类型的形参,否则会报错,就比如下面注释掉的情况
//int operator(int a,int n)
//{
//    return a-n;
//}int operator+(const A& a, int& n)
{return a._a + n;  //这里成员变量需要放公有,否则不能直接访问
}int main()
{A a;int n = 10;cout << a + n << endl;return 0;
}

 运算符重载以后,优先级和结合性与对应的内置类型运算符保持一致的。

不能重载的情况: 

  1. 不能用没有的符号创建新的操作符(运算符)
    //例如:
    operator@ //就不能创建

  2. 有五个运算符是不能重载的:.*    ::    sizeof     ?:   . 

 后四个是经常用到的,第一个非常用,下面单独拿出来解释

//函数指针较为特别,typedef类型需要这样写
typedef void (*PF)(); class A
{
public:void func(){cout << "A::func()" << endl;}
};//若typedef成员函数指针类型,就加个指定类域即可
typedef void(A::*PF)();// .*运算符就用于以下方式回调函数的场景(成员函数回调)
int main()
{//成员函数要加&才能取到函数指针PF pf = &A::func;      //这里相当于 void(A::*pf)() = &A::func;A a;(a.*pf)();return 0;
}

(深入理解指针) 

//这里是普通全局函数回调的形式
(*pf)();

所以这个符号的意义是对象调用成员函数指针时,成员函数的回调 (注意:考虑有隐含的this指针,不能显示写形参和传实参)

重载++运算符时,前置++与后置++的运算符重载函数名都是operator++,无法来区分。 C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分。

 

class Date
{
public://构造函数Date(int year = 1900,int month =1,int day=1):_year(year),_month(month),_day(day){}//前置++Date& operator++(){cout << "这里是前置++" << endl;  //这里演示一下,先不实现return *this;}//后置++Date& operator++(int)  //这里的加不加形参名都可以,必须要有int (只要整型){Date tmp;cout << "这里是后置++" << endl;  return tmp;}private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2;++d1;d2++;d2.operator++(10); //这里传不传值都可以,因为那个int一般不接收。 
}

注意:重载的++运算符也要与内置类型++运算符规则一致(前置++:先++,再返回++后的结果,不产生拷贝;后置++:进行++,++之后返回的是++前的结果,会产生拷贝。所以,一般开以选择前置++来减少拷贝)。

我们实现日期类,还想需要可以对这个日期cout和cin来方便输出和输入,所以<<和>>也是可以重载的,但是需要重载成全局函数,重载为全局函数把ostream/istream放到第一个形参位置就可以了,第二个形参位置当类类型对象。否则会有隐含的this指针,导致调用时变成:对象<<cout ,不符合使用习惯和可读性(想要的是:cout<<对象)

2.日期类实现代码

我们可以声明定义分离来实现,分别创建Date.h头文件和Date.cpp用来定义头文件声明的函数。

//Date.h#include<iostream>
#include <assert.h>
using namespace std;
class Date
{//友元函数声明 --这两个全局函数就可以访问对象的私有成员friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);
public://构造函数,若没有传参 就是默认构造 全缺省函数Date(int year = 1900, int month = 1, int day = 1);//拷贝构造Date(const Date& d);void Print()const;  //打印日期bool CheckDate() const;  //检查日期输入是否正确//频繁调用的建议直接定义类里面,默认inline内联int GetMonthDay(int year, int month)const  {assert(month > 0 && month < 13);static int monthDay[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 }; //因为没有0月,将0置-1空出来//用static修饰是因为这个数组会频繁调用,直接放在静态区if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){return 29;}return monthDay[month];}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;//d1 = d2Date& operator=(const Date& d);Date operator+(int day) const;Date& operator+=(int day);Date operator-(int day) const;Date& operator-=(int day);// d1++;// d1.operator++(0);Date operator++(int);// ++d1;// d1.operator++();Date& operator++();// d1--;// d1.operator--(0);Date operator--(int);// --d1;// d1.operator--();Date& operator--();// d1 - d2int operator-(const Date& d) const;//void operator<<(ostream& out);Date* operator&() //取地址运算符重载 //普通对象返回类型Date*{return (Date*)0x2673FF40;  //返回假地址}const Date* operator&() const  //取地址运算符重载 //const对象要调用const成员函数要返回类型const Date*{return (Date*)0x2673FE30; //返回假地址	}
private:int _year;int _month;int _day;
};//重载流插入流提取
// ostream/istream类型对象不能传值只能传引用,否则会报错,因为c++不支持它们的拷贝构造。
//要写成全局函数 否则调用的时候需要这样写:d1<<cout    d1>>cin   用着会比较别扭
//因为如果写成了成员函数第一个参数有隐藏的this指针,并且不能修改
//cout是ostream类型的 (也可以用void返回类型但不建议)
//cin默认关联cout
//在cin进行i/o操作前会刷新cout的缓冲区//流插入
ostream& operator<<(ostream& out, const Date& d);//流提取
//这里第一个参数不能加const修饰了,因为提取的值要放日期类对象里
istream& operator>>(istream& in, Date& d);
//Date.cpp#include "Date.h"
bool Date::CheckDate() const//检查日期正确
{if (_month < 1 || _month > 12|| _day < 1 || _day > GetMonthDay(_year, _month)){return false;}else{return true;}
}Date::Date(int year, int month, int day)  //构造函数(全缺省,前面声明中已经给了缺省值,定义这里就不能再写出来了)
{_year = year;_month = month;_day = day;if (!CheckDate()){cout << "非法日期:";Print();}cout << endl;
}Date::Date(const Date& d)   //拷贝构造
{_year = d._year;_month = d._month;_day = d._day;
}void Date::Print()const  //打印日期
{cout << _year << "/" << _month << "/" << _day << endl;
}//d1 = d2
Date& Date::operator=(const Date& d)
{if (*this != d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}Date& Date::operator+=(int day)  //加等可以让自己改变    
{//先判断day是否是负数 ---- _day+(-day)==_day-dayif (day < 0){return *this -= (-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) const //只加不能改变自己  
//这里不能用传引用返回 因为这里用const修饰只读取不想改变自己,引用会把权限放大
{Date tmp = *this;tmp += day;return tmp;
}//- -=同+ +=
Date Date::operator-(int day) const
{Date tmp = *this;tmp -= day;return tmp;
}
Date& Date::operator-=(int day)
{//先判断day是否是负数  --->_day-(-day)==_day+dayif (day < 0)         {return *this += (-day);}_day -= day;while (_day <= 0){--_month;if (_month == 0){_month = 12;--_year;}_day += GetMonthDay(_year, _month);}return *this;
}//d1<d2
bool Date::operator<(const Date& d) const  //这里传引用 d就是d2的别名 也就是d2的本身
{if (_year < d._year){return true;}else if (_year == d._year){if (_month < d._month){return true;}if (_month == d._month){return _day < d._day;}}return false;
}//d1<=d2
//这里就可以复用函数了
bool Date::operator<=(const Date& d) const
{return *this < d || *this == d;
}
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 (_year == d._year) && (_month == d._month) && (_day == d._day);
}
bool Date::operator!=(const Date& d) const
{return !(*this == d);
}// d1++;  后置++有拷贝
// d1.operator++(0);
//用的时候括号里只要是整数都可以
Date Date::operator++(int)  // 加不加形参名都可以,一般不接收
{Date tmp = *this;*this += 1;return tmp;
}// ++d1;  //前置++无拷贝
// d1.operator++();
Date& Date::operator++()
{*this += 1;return *this;
}//-- 同理++
// d1--;
// d1.operator--(0);
Date Date::operator--(int)
{Date tmp = *this;*this -= 1;return tmp;
}// --d1;
// d1.operator--();
Date& Date::operator--()
{*this -= 1;return *this;
}// d1 - d2
int Date::operator-(const Date& d) const
{Date max = *this;  //假设第一个大Date min = d;   //第二个小int flag = 1;   //等于1时表示第一个大第二个小if (*this < d){max = d;min = *this;flag = -1;       //第二个大第一个小}int n = 0;while (min != max){++min;++n;}return n *= flag;
}//流插入cout<<d1<<d2
ostream& operator<<(ostream& out, const Date& d)
{cout << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}//流提取cin>>d1>d2
istream& operator>>(istream& in, Date& d)
{cout << "请依次输入年月日:>";in >> d._year >> d._month >> d._day;//检查日期是否非法if (!d.CheckDate()){cout << "非法日期: " << d << "请重新输入" << endl;while (1){cout << "请依次输入年月日:>";in >> d._year >> d._month >> d._day;if (!d.CheckDate()){cout << "输入日期非法:";d.Print();cout << "请重新输入!!!" << endl;}else{break;}}}return in;
}

 有些注意事项,在上面代码的实现注释中有一些解释。

制作不易,若有不足之处或出问题的地方,请各位大佬批评指正 ,感谢大家的阅读支持!!!

相关文章:

C++ —— 实现一个日期类

目录 一. 对日期类的介绍 二. 实现日期类 1. 运算符重载 2.日期类实现代码 一. 对日期类的介绍 通过对类和对象&#xff08;这里链接是类和对象的介绍&#xff09;的学习&#xff0c;类就是一种模型一样的东西&#xff0c;是对象一种抽象的描述。所以实现日期类&#xff0…...

Java全栈经典面试题剖析5】JavaSE高级 -- 集合

目录 面试题3.18 Java中有多少种数据结构&#xff0c;分别是什么&#xff1f; 面试题3.19 List、Set和Map的区别&#xff1f; 面试题3.20 List遍历方式有多少种 面试题3.21 Arraylist&#xff0c;Vector和Linkedlist 的区别 面试题3.22 Collection和Collections的区别 面试…...

python中如何获取对象信息

目录 一、获取对象类型 二、使用isinstance()函数 三、使用dir()函数 四、使用对象的__dict__属性&#xff08;适用于大多数自定义对象&#xff09; 五、使用文档字符串&#xff08;__doc__属性&#xff09;获取对象的文档信息 一、获取对象类型 使用type()函数&#xff…...

逐行讲解transformers中model.generate()源码

目录 简介输入程序model.generate()输入参数1. 创建生成参数对象 generation_config2. 初始化部分输入参数3. 定义模型输入4. 定义其他模型参数5. 准备 input_ids6. 准备 max_length7. 确定生成模式8. 准备 logits 处理器9. 准备 stopping 处理器10. 执行生成 self._sample1. 先…...

小白对时序数据库的理解

一、什么是时序数据库&#xff1f; 时序数据库&#xff08;Time Series Database&#xff0c;TSDB&#xff09;是一种专门用于存储、处理和分析时间序列数据的数据库管理系统。时间序列数据是按时间顺序记录的数据&#xff0c;通常由各种设备和传感器生成&#xff0c;例如智慧…...

打开游戏提示丢失(或找不到)XINPUT1_3.DLL的多种解决办法

xinput1_3.dll是一个动态链接库&#xff08;DLL&#xff09;文件&#xff0c;它在Windows操作系统中扮演着重要的角色。该文件作为系统库文件&#xff0c;通常存放于C:\Windows\System32目录下&#xff08;对于32位系统&#xff09;或C:\Windows\SysWOW64目录下&#xff08;对于…...

netty的网络IO模型

参考&#xff1a; 聊聊Netty那些事儿之从内核角度看IO模型...

电子木鱼小游戏小程序源码系统 带完整的安装代码包以及搭建部署教程

系统概述 在快节奏的生活中&#xff0c;人们越来越注重内心的平静与放松。电子木鱼小游戏小程序正是基于这一需求而诞生的&#xff0c;它将传统的木鱼文化与现代科技相结合&#xff0c;为用户提供了一个简单、方便、有趣的冥想与放松工具。通过敲击屏幕上的虚拟木鱼&#xff0…...

支付域——交易系统设计

摘要 交易是业务流转的基础,其中交易系统和订单系统的设计至关重要。交易系统需确保安全、高效与稳定。在设计时,要考虑支付方式的多样性及兼容性,保障资金流转的准确与安全。同时,应具备良好的风险控制机制,防范欺诈等风险。订单系统则负责记录和管理交易的全过程。需清…...

IBus 和 Fcitx 框架下的rime输入法引擎

Rime 输入法引擎 Rime&#xff08;中州韵输入法引擎&#xff09;&#xff1a;这是一个跨平台的输入法引擎&#xff0c;支持多种输入法方案&#xff0c;如拼音、五笔、注音等。Rime本身不提供前端界面&#xff0c;它需要与输入法框架&#xff08;如IBus或Fcitx&#xff09;结合…...

Java基础-JVM

JVM构成部分 类加载系统 类加载子系统的作用是将磁盘中的.class文件加载到内存当中。工作过程如下&#xff1a; 加载&#xff1a;通过类全路径名获取二进制字节流&#xff1b;将这个二进制字节流代表的数存储构转化为方法区运行时数据结构&#xff1b;在内存生成一个代表该类的…...

集成学习:投票法、提升法、袋装法

集成学习&#xff1a;投票法、提升法、袋装法 目录 &#x1f5f3;️ 投票法 (Voting)&#x1f680; 提升法 (Boosting)&#x1f6cd;️ 袋装法 (Bagging) 1. &#x1f5f3;️ 投票法 (Voting) 投票法是一种强大的集成学习策略&#xff0c;它通过将多个模型的预测结果进行组合…...

波浪理论、江恩理论、价值投资的结合

结合波浪理论、江恩理论和价值投资&#xff0c;需要理解这三种方法的核心原理和应用方式。下面详细解析如何将它们融合在一起&#xff0c;形成一个更全面的投资策略&#xff1a; 1. 基本概述 波浪理论&#xff1a;由艾略特提出&#xff0c;通过分析市场波动的五个上升浪和三个…...

LRDDR4芯片学习(三)——命令和时序

ddr command: activate commandrefresh commandprecharge commandwrite/read commandburst write/read commandMRR/MRW command 一、Activate命令 在读写命令之前&#xff0c;必须要发送Activate命令&#xff0c;由ACTIVATE-1、ACTIVATE-2命令组成。ACTIVATE命令中包含了BA[…...

【趣学C语言和数据结构100例】

【趣学C语言和数据结构100例】 问题描述 61.假定采用带头结点的单链表保存单词,当两个单词有相同的后缀时,可共享相同的后缀存储空间,例如,loading 和 being 的存储映像如下图所示,设 strl 和 str2 分别指向两个单词所在单链表的头结点,链表结点结构为 data next。请设计…...

linux卸载数据库(最为完整的卸载方式)

1.首先检查是否安装了MySQL组件 我们可以看到有五个与mysql相关的组件 2.卸载前关闭MySQL服务 systemctl stop mysqld systemctl status mysqld 3.收集MySQL对应的文件夹信息 whereis mysql 4.卸载删除MySQL各类组件 #例如 rpm -ev --nodeps mysql-community-libs-5.7.…...

H7-TOOL的LUA小程序教程第15期:电压,电流,NTC热敏电阻以及4-20mA输入(2024-10-21,已经发布)

LUA脚本的好处是用户可以根据自己注册的一批API&#xff08;当前TOOL已经提供了几百个函数供大家使用&#xff09;&#xff0c;实现各种小程序&#xff0c;不再限制Flash里面已经下载的程序&#xff0c;就跟手机安装APP差不多&#xff0c;所以在H7-TOOL里面被广泛使用&#xff…...

使用梧桐数据库进行销售趋势分析和预测

在当今竞争激烈的商业环境中&#xff0c;企业需要深入了解销售数据&#xff0c;以便做出明智的决策。销售趋势分析和预测是帮助企业把握市场动态、优化库存管理、制定营销策略的重要工具。本文将介绍如何使用SQL来创建销售数据库的表结构&#xff0c;插入示例数据&#xff0c;并…...

SQLITE排序

最终实现的效果&#xff1a;先查询第一层2列开始的1、4、2、3排&#xff0c;再查询第三列、四列...,然后第二层... 入库 排序优先级&#xff1a;层>列>排(1>2,4>3) 最终排的优先级 1>4>2>3 ORDER BY rack.rackLayer,rack.rackColumn, CASE rack.rackRowW…...

python的文件操作

文件操作 1.打开文件 2.读取文件内容 3.写入文件内容 4.关闭文件 要打开文件&#xff0c;可以使用open()函数并指定文件路径和模式。 file open("example.txt", "r") # 打开了一个名为"example.txt"的文件&#xff0c;并将其赋值给变量file。第…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

Java - Mysql数据类型对应

Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

postgresql|数据库|只读用户的创建和删除(备忘)

CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

如何配置一个sql server使得其它用户可以通过excel odbc获取数据

要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据&#xff0c;你需要完成以下配置步骤&#xff1a; ✅ 一、在 SQL Server 端配置&#xff08;服务器设置&#xff09; 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到&#xff1a;SQL Server 网络配…...

【Linux】Linux安装并配置RabbitMQ

目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的&#xff0c;需要先安…...