C++实现日期类(类和对象总结与实践)

头文件:

首先,在头文件Date.h中声明日期类

先上代码,然后一步一步解析每个函数

#include<iostream>
#include<assert.h>
using namespace std;class Date
{public:void Print() const;// 获取某年某月的天数// 这个函数会被频繁的调用,所以inline(类的里面)int GetMonthDay(int year, int month){// static,后面进来就不会一直初始化的,第一次就初始化,所以可以提升效率static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };int day = days[month];if (month == 2&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){day += 1;}return day;}// 检查日期是否合法bool CheckDate(){if (_year >= 1&& _month > 0 && _month < 13&& _day>0 && _day <= GetMonthDay(_year, _month)){return true;}else{return false;}}// 构造函数会频繁的调用,所以直接放在类里面定义,作为inlineDate(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;assert(CheckDate());}private:int _year;int _month;int _day;
};

Print函数

void Print() const;

Print函数就是用来打印特定格式日期的函数。 

在C++中,将成员函数声明为const表示该函数不会修改对象的状态,也就是说,它不会修改对象的成员变量。这对于确保对象的不可变性和代码的健壮性非常重要。

为什么要用const?

  • 保护成员变量:声明为const可以确保Print函数在执行过程中不会修改类的成员变量_year, _month_day。这是因为Print函数的职责只是打印信息,不应该对对象的状态进行任何修改。
  • 提高代码可读性和可维护性:通过在函数签名中显式地添加const,可以提高代码的可读性,表明这个函数不会修改对象状态。
  • 允许const对象调用:如果一个对象是const类型,例如:
  • const Date myDate(2023, 5, 27);
    

只有当成员函数是const的情况下,才能调用这个函数。因此,如果Print函数没有声明为const,那么就不能在const对象上调用它。

GetMonthDay函数

int GetMonthDay(int year, int month)

首先静态数组days中包含了每个月份的天数。数组的索引对应月份,其中days[1]表示1月的天数31天,days[2]表示2月的天数28天,依此类推。使用静态数组是因为它只需要初始化一次,之后的函数调用中不会重复初始化,从而提高了效率。

然后通过days[month]获取指定月份的天数,并将其赋值给day变量。

后面的if判断是否为闰年,并且月份是否为2月。

闰年的判断条件是:年份能被4整除且不能被100整除,或者年份能被400整除。如果满足条件,则说明是闰年且当前月份是2月,因此2月有29天,将day增加1。

这里有个小细节,month == 2在前面,这可以先判断月份是否为2,可以提高效率。

CheckDate函数

很好理解,就是判断月日是否超出界限。比如月份不能超过12。

接下来,需要重载Date的运算符,比如+-

	// 声明// 不改变的都应该加constbool 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;Date operator+(int day) const;Date& operator+=(int day);// ++d1;// d1++;// 直接按特性重载,无法区分// 特殊处理,使用重载区分,后置++重载增加一个int参数根前置构成函数重载进行区分Date& operator++(); // 前置Date operator++(int); // 后置// d1 - 100Date operator-(int day);Date& operator-=(int day);// 直接按特性重载,无法区分// 特殊处理,使用重载区分,后置++重载增加一个int参数根前置构成函数重载进行区分Date& operator--(); // 前置Date operator--(int); // 后置// d1 - d2int operator-(const Date& d) const;

全局函数

 全局
//ostream& operator<<(ostream& out, const Date& d); // 运算符重载// 内联不要声明和定义分离
// 因为这个函数要被频繁调用,所以内联
// 流插入重载
inline ostream& operator<<(ostream& out, const Date& d) // 运算符重载
{// out就是cout的别名out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}// 流提取重载
inline istream& operator>>(istream& in, Date& d) // 运算符重载
{in >> d._year >> d._month >> d._day;assert(d.CheckDate());return in;
}

对于插入运算符<<重载:

  • 输出 Date 对象:这个函数重载了插入运算符 <<,使得可以通过 ostream 对象(例如 cout)来输出 Date 对象的内容。
  • ostream& out输出流对象的引用,通常是cout。因为传递的是引用,所以可以直接操作流对象。const Date& d:常量Date对象的引用,需要输出的Date对象。
  • 支持链式操作:返回ostream&类型使得可以将多个输出操作链接在一起,例如cout << date1 << " " << date2;

对于提取运算符>>重载:

  • 输入 Date 对象:这个运算符重载使得可以通过istream对象(如cin)来输入Date对象的内容。
  • istream& in输入流对象的引用,通常是cin。因为传递的是引用,所以可以直接操作流对象。Date& d:非constDate对象的引用,需要输入的Date对象。
  • 支持链式操作:返回istream&类型使得可以将多个输入操作链接在一起,例如cin >> date1 >> date2;,连续输入多个Date对象。

通过重载运算符,可以直接使用 cout << datecin >> date 进行日期的输入输出,简化了代码编写。

由于这两个函数是在类的外面声明的,所以需要在Date里面定义为友元函数

	// 友元函数 -- 这个函数内部可以使用Date对象访问私有保护成员friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);

Date.cpp

在这个文件中就可以实现在头文件中声明的那些函数了

对于Print函数

// 任何一个类,只需要写一个>= 或者 <= 重载,剩下的比较运算符重载复用即可// const修饰的是this指针指向的内容,也就是保证了成员函数内部不会修改成员变量
// const对象和非const对象都可以调用这个函数
// void Date::Print(const Date* const this)
void Date::Print() const
{cout << _year << "/" << _month << "/" << _day << endl;
}

任何一个类,只需要写一个>= 或者 <= 重载,剩下的比较运算符重载复用即可

==运算符

// d1 = d2
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 _year != d._year//	&& _month != d._month//	&& _day != d._day;return !(*this == d);
}

在C++中,this指针是指向当前对象的指针。每个成员函数都隐式地包含一个this指针,用于访问调用该函数的对象。利用this指针,我们可以在成员函数中引用当前对象。

*this表示当前对象的引用。通过解引用this指针,可以得到当前对象的引用,进而可以将当前对象与另一个对象进行比较。

在定义!=运算符重载时,直接调用==运算符重载,避免了重复代码。

return !(*this == d);这行代码首先通过*this获取当前对象的引用,然后调用==运算符重载比较当前对象与传入对象d是否相等,并取反得到不相等的结果

>运算符

// d1 > d2
bool Date::operator>(const Date& d) const
{if (_year > d._year|| (_year == d._year && _month > d._month)|| (_year == d._year && _month == d._month && _day > d._day)) // 年大{return true;}else{return false;}
}

>=运算符

// d1 >= d2
bool Date::operator>=(const Date& d) const
{return (*this > d || *this == d);
}
  1. 调用>运算符

    *this > d:使用已经定义好的>运算符重载函数来判断当前对象是否大于传入的对象d
  2. 调用==运算符

    *this == d:使用已经定义好的==运算符重载函数来判断当前对象是否等于传入的对象d
  3. 逻辑运算

    (*this > d || *this == d):如果当前对象大于传入的对象,或者当前对象等于传入的对象,则返回true,否则返回false

<运算符

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

<反过来就是>=,所以直接调用>=运算符然后取反即可

<=运算符

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

<=反过来就是>,所以直接调用>运算符然后取反即可

+=运算符

// d2 += d1 += 100
Date& Date::operator+=(int day) 
{if (day < 0){return *this -= -day;}_day += day;while (_day > GetMonthDay(_year, _month)) // 如果day超过了month或year的范围{_day -= GetMonthDay(_year, _month);++_month;if (_month == 13){_year++;_month = 1;}}return *this; // 出了作用域用引用返回,因为返回的是自己this
}

首先

  • 如果 day 为负数,则调用 -= 运算符重载函数,将负天数转换为正天数并减去。
  • *this -= -day:通过 this 指针调用 -= 运算符重载函数,返回当前对象的引用

然后增加天数

  • day 加到当前日期的 _day 成员变量中。

然后处理日期溢出

  • 使用 while 循环处理日期溢出问题,即如果天数超过了当前月份的天数,则进行调整。
  • GetMonthDay(_year, _month):获取当前年份和月份的天数。
  • _day -= GetMonthDay(_year, _month):如果 _day 超过当前月份的天数,则减去该月份的天数,并将月份加一。
  • if (_month == 13):如果月份超过12月,则将年份加一,并将月份重置为1月。

最后返回当前对象引用 *this

  • 返回当前对象的引用以支持链式调用。例如,d1 += 100 后,可以继续进行其他操作。

+运算符

// d1 + 100
Date Date::operator+(int day) const
{//Date ret = *this; // 也是拷贝构造Date ret(*this); // 拷贝构造ret += day;return ret;
}

首先

  • 使用拷贝构造函数创建一个新的 Date 对象 ret,并将当前对象 *this 的值赋给它。这样 ret 就是当前对象的副本。
  • *this 表示当前对象的引用,通过 Date ret(*this); 将当前对象复制给 ret,以便在不修改当前对象的情况下进行操作。

然后

  • 使用 += 运算符重载函数将 day 天数加到 ret 对象中。这个操作会修改 ret 对象,但不会影响当前对象 *this

++运算符(前置和后置)

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

前置的很好理解

对于后置++

首先

  • 使用拷贝构造函数创建一个新的 Date 对象 tmp,并将当前对象 *this 的值赋给它。这样 tmp 就是当前对象的副本,记录了递增前的状态。
  • *this 表示当前对象的引用,通过 Date tmp(*this); 将当前对象复制给 tmp

然后递增当前对象的日期

  • 使用已经定义的 += 运算符重载函数将当前对象的日期增加一天。
  • *this 表示当前对象,通过 *this += 1; 调用 += 运算符,将日期增加一天。

然后返回原始对象的副本

  • 返回先前创建的副本 tmp,它包含了递增前的日期。
  • 这种设计符合后置递增运算符的语义:先返回原值,然后再递增。

-=运算符

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

 首先

  • 如果 day 为负数,则将其转换为正数,并调用 += 运算符重载函数,将负天数转换为加法操作。
  • *this += -day:通过 this 指针调用 += 运算符重载函数,返回当前对象的引用。

然后

  • day 从当前日期的 _day 成员变量中减去。

然后处理日期溢出

  • 使用 while 循环处理日期溢出问题,即如果天数小于等于0,则进行调整。
  • --_month:将月份减一。
  • if (_month == 0):如果月份减到0,则年份减一,并将月份重置为12月。
  • _day += GetMonthDay(_year, _month):将 _day 加上前一个月的天数。

最后

  • 返回当前对象的引用,以支持链式调用。例如,d1 -= 30 后,可以继续进行其他操作。

- 运算符

Date Date::operator-(int day)
{// 借位Date ret = *this; // 拷贝构造ret -= day;return ret;
}

首先

  • 使用拷贝构造函数创建一个新的 Date 对象 ret,并将当前对象 *this 的值赋给它。这样 ret 就是当前对象的副本。
  • *this 表示当前对象的引用,通过 Date ret = *this; 将当前对象复制给 ret

然后

  • 使用已经定义的 -= 运算符重载函数将 day 天数从 ret 对象的日期中减去。
  • *this 表示当前对象,通过 ret -= day; 调用 -= 运算符,将 day 减去。
为什么要拷贝构造一个对象
  • 确保不修改当前对象:通过拷贝构造函数创建一个当前对象的副本,可以确保对日期的操作不会修改当前对象,从而满足纯函数的性质,即不修改输入对象,而是返回一个新的结果对象。
  • 实现纯函数:重载的减法运算符需要返回一个新的对象而不是修改当前对象。为了实现这一点,必须创建一个当前对象的副本,对副本进行操作,然后返回副本。
  • 避免副作用:确保对当前对象的任何修改都不会影响调用者,从而避免副作用,提高代码的健壮性和可维护性。

--操作符(前置和后置)

Date& Date::operator--() // 前置
{return *this -= 1;
}
Date Date::operator--(int) // 后置,返回之前的值
{Date tmp(*this);*this -= 1;return tmp;
}

前置很容易理解,后置--则和后置++类似,这里不再赘述

-操作符(日期相减)

// d1 - d2
int Date::operator-(const Date& d) const// 日期相减
{int flag = 1;// 假设第一个大,第二个小Date max = *this;Date min = d;if (*this < d){max = d;min = *this;flag = -1;}int n = 0;while (min != max){++min;++n;}return n * flag;
}

首先,初始化一个flag

  • 标志变量 flag 用于确定结果的正负。当当前对象大于传入对象时,结果为正;否则为负。

然后

  • 假设当前对象 *this 大于传入对象 d。将当前对象赋给 max,将传入对象赋给 min

判断

  • 如果当前对象*this小于传入对象d,则交换 maxmin,并将 flag 设置为 -1,表示结果为负。

然后计算天数差

  • 使用 while 循环,直到 min 等于 max。在每次循环中,递增 min,并增加计数器 n。这样就计算出了 minmax 的天数差。

最后返回结果

  • 返回 n 乘以 flag,得到正确的天数差。flag 确保结果的正负正确。如果当前对象*this大于传入对象d,那么flag为1,天数差为正数,反之亦然。

Test.cpp

至此所有日期类的函数和运算符重载都写出来了,下一步可以开始测试。

测试结果就不放出来了,有兴趣可以自己测试一下并且与真实日期比较一下是否正确,下面列举几个测试的选项:

void TestDate1()
{Date d1(2023, 8, 24);Date d2(2024, 7, 25);Date d3(2021, 1, 18);cout << (d1 < d2) << endl;cout << (d1 < d3) << endl;cout << (d1 == d3) << endl;cout << (d1 > d3) << endl;
}
void TestDate2()
{Date d1(2022, 7, 24);d1 += 5;d1.Print();d1 += 50; // 跨月d1.Print();d1 += 500; // 跨年d1.Print();d1 += 5000; // 跨闰年d1.Print();}
void TestDate3()
{Date d1(2022, 7, 25);(d1 - 4).Print();(d1 - 40).Print();(d1 - 400).Print();(d1 - 4000).Print();// d1 - d2Date d2(2022, 7, 25);Date d3(2023, 2, 15);cout << d2 - d3 << endl;cout << d3 - d2 << endl;}
void Test4()
{Date d1(2022, 7, 25);Date d2(2022, 7, 26);cout << d1 << d2;cin >> d1 >> d2;cout << d1 << d2;
}

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

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

相关文章

做好随时离开的准备:前一天还在为618加班到凌晨,第二天就被裁了

今日感悟 最近&#xff0c;一则令人唏嘘的新闻在网络上引起了广泛关注&#xff1a;一名员工前一天还在为618大促活动加班到凌晨&#xff0c;身心疲惫&#xff0c;然而第二天却收到了裁员通知&#xff0c;顿时陷入了失业的困境。 这则新闻不仅揭示了职场竞争的残酷现实&#xff…

有关服务器安全的反思

文章目录 前言MySQL数据库的安全物理服务器总结 前言 人都说学的越多&#xff0c;不懂的东西也就越多&#xff0c;很多人都会有这个感受&#xff0c;面对信息爆炸的互联网时代&#xff0c;有种“学不完&#xff0c;根本学不完”沧桑无力感&#xff0c;最近有关服务器安全的了解…

Flutter 中的 ConstrainedBox 小部件:全面指南

Flutter 中的 ConstrainedBox 小部件&#xff1a;全面指南 在 Flutter 的世界中&#xff0c;布局小部件扮演着至关重要的角色&#xff0c;它们帮助开发者以声明式的方式构建用户界面。ConstrainedBox 是其中一种强大的布局小部件&#xff0c;它允许开发者对子组件的尺寸施加额…

Vistual Studio Release模式 调试方法

在开发过程中&#xff0c;有时会遇到这样的问题&#xff1a;代码在Debug模式下运行良好&#xff0c;但在Release模式下运行却出现错误。通常&#xff0c;这类问题往往与缓冲区越界等内存管理相关的错误有关。在Release模式下&#xff0c;由于编译器的优化&#xff0c;错误更容易…

js全国省市区JSON数据(全)

AreaJson 就是全国省市区的具体数据信息&#xff0c;下面我自定义了一些方法&#xff0c;获取数据用的&#xff0c;不需要的可以删掉&#xff0c;只拿JSON内的数据即可 const AreaJson [{"name": "北京市","city": [{"name": "…

数据结构算法题day02

数据结构算法题day02 【day02】思想代码 【day02】 将两个有序顺序表合并为一个新的有序顺序表&#xff0c;并由函数返回结果顺序表。思想 两个有序顺序表&#xff0c;AB本身就是由大到小或者由小到大排序的顺序表。 思路比较经典&#xff0c;希望大家记忆 将AB中较小的依次存…

Git钩子(Hooks)之commit之前自动执行脚本

介绍 官方文档&#xff1a; 英文&#xff1a;https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks中文&#xff1a;https://git-scm.com/book/zh/v2/自定义-Git-Git-钩子 下面只复制了pre-commit部分文档&#xff0c;其他详见官方文档。 Git Hooks Like many other…

【绝地求生game】

编写一个完整的《绝地求生》这样的游戏程序代码是一个庞大的工程&#xff0c;涉及到成千上万行的代码和复杂的多模块协作。在这里&#xff0c;我可以提供一个非常简化的示例&#xff0c;用于演示游戏编程中可能用到的基本概念&#xff0c;比如玩家移动、基本物理和简单的游戏逻…

【Java面试】四、MySQL篇(上)

文章目录 1、定位慢查询2、慢查询的原因分析3、索引3.1 数据结构选用&#xff1a;二叉树 & 红黑树3.2 数据结构选用&#xff1a;B树 4、聚簇索引、非聚簇索引、回表查询4.1 聚簇索引、非聚簇索引4.2 回表查询 5、覆盖索引、超大分页优化5.1 覆盖索引5.2 超大分页处理 6、索…

联发科MT8370平台Genio 510物联网应用程序处理器详细规格参数

MT8370是一款高度集成、功能强大的平台&#xff0c;专为各种人工智能(AI)和物联网(IoT)用例而设计&#xff0c;这些用例需要高性能边缘处理、先进的多媒体和连接功能、多个高分辨率摄像头、连接的触摸屏显示器以及多任务高级操作系统(HLOS)的使用。http://Genio 510 (MT8370) E…

Mybatis源码剖析

文章目录 一、前置1.1概念ORMSqlSession会话 二、快速入门2.1 SpringBoot整合Mybatis2.2 XML配置2.2.1 路径位置2.2.2 名称2.2.3 configuration标签内容环境environments标签映射器mappers标签 2.3 Mapper接口2.3.1 单Mybatis项目2.3.2 SpringBoot整合mybatis2.3.3 m整合mybati…

字符串函数(2)<C语言>

前言 快一周没更博客了&#xff0c;最近有点忙&#xff0c;今天闲下来了&#xff0c;还是不行&#xff0c;继续干&#xff0c;书接上回继续介绍字符串函数&#xff1a;strncpy()、strncat()、strcmp()、strtok()使用、strstr()使用以及模拟实现、strerror()使用。 strncpy()、s…

blender serpens3 个人总结

Serpens 全节点个人备注 快捷键 &#xff1a;shift v&#xff1a; 从复制版 添加执行操作&#xff08;blender任何执行动作按钮&#xff0c;右键可以获取操作命令&#xff09; 概念分析&#xff1a; 属性&#xff08;Properties&#xff09;&#xff1a;用于定义持久性数据…

揭秘网络编程:同步与异步IO模型的实战演练

摘要 ​ 在网络编程领域&#xff0c;同步(Synchronous)、异步(Asynchronous)、阻塞(Blocking)与非阻塞(Non-blocking)IO模型是核心概念。尽管这些概念在多篇文章中被广泛讨论&#xff0c;它们的抽象性使得彻底理解并非易事。本文旨在通过具体的实验案例&#xff0c;将这些抽象…

在React中使用Sass实现Css样式管理-10

0. 什么是Sass Sass(Syntactically Awesome Stylesheets)是一个 CSS 预处理器&#xff0c;是 CSS 扩展语言&#xff0c;可以帮助我们减少 CSS 重复的代码&#xff0c;节省开发时间&#xff1a; Sass 引入合理的样式复用机制&#xff0c;可以节约很多时间来重复。支持变量和函…

【HM】简单说明白:装饰器@State、@Prop、@Link、@Provide、@Consume修饰变量,@Watch监听变量状态发生变化

首先要明白什么是“状态变量”&#xff1f;即被状态装饰器&#xff08;State、Prop、Link、Provide、Consume&#xff09;修饰的变量&#xff0c;比如 State str : string; str就是状态变量。状态变量值的改变会引起UI界面重新渲染。 State State装饰的变量&#xff0c;是私…

C++之“流”-第2课-C++和C标准输入输出同步

为什么C和C的标准输入输出不同步时&#xff0c;数据会混乱&#xff1f;同步会带来多大性能损失&#xff1f;为什么说这个损失通常不用太在乎&#xff1f; 0. 课堂视频 C之“流”-第2课&#xff1a;和C输入输出的同步 1. 理解cin和cout的类型与创建过程 std::cout 是std::ostre…

Ubuntu系统Discover软件中心简介

Discover软件中心是Ubuntu操作系统中默认的软件管理工具&#xff0c;它提供了一个图形用户界面(GUI)来帮助用户浏览、搜索、安装和卸载软件包。Discover软件中心是Ubuntu软件中心(Ubuntu Software Center)的继承者&#xff0c;它在Ubuntu 16.04 LTS版本中首次被引入&#xff0c…

添加、修改和删除字典元素

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 由于字典是可变序列&#xff0c;所以可以随时在字典中添加“键-值对”。向字典中添加元素的语法格式如下&#xff1a; dictionary[key] value 参数…

You don‘t have enough free space或者no space left on device异常

1.磁盘空间不足 Linux安装软件显示 You dont have enough free space 或者docker拉镜像时&#xff0c;出现磁盘空间不足的情况 no space left on device 如果你是ubuntu系统。查看磁盘空间 df -h 多半是这个目录满了/dev/mapper/ubuntu--vg-ubuntu--lv 大多情况我们只希望扩…