【C++项目实战】类和对象入门实践:日期类实现万字详解

           💓 博客主页:倔强的石头的CSDN主页 

           📝Gitee主页:倔强的石头的gitee主页

            ⏩ 文章专栏:《C++项目实战》

                                  期待您的关注

 

47f09392526c71b5885ec838a3ea7ffe.gif

目录

引言

介绍

一、类的设计

 二、成员函数的实现

 🍃构造函数、析构函数、拷贝构造函数和赋值运算符重载

🍃友元函数:重载>>和<<

 🍃日期合法性检查函数

🍃获取某年某月的天数

🍃比较运算符重载

🍃 日期加减操作

🍃自增自减操作

🍃日期差计算

结语


 

引言

在编程的世界里,类和对象是面向对象编程(OOP)的核心概念。它们为我们提供了一种组织和封装数据及其相关操作的强大机制。类可以被视为一种蓝图或模板,它定义了对象的属性和方法。而对象则是根据这些类创建的具体实例,它们具有自己的状态(通过属性表示)和行为(通过方法实现)。

 

日期处理是编程中常见且重要的一个领域。无论是在日志记录、事件管理,还是在日程安排中,日期都扮演着至关重要的角色。通过实现一个日期类,我们不仅可以深入理解类和对象的概念,还能将这些理论知识应用于解决实际问题。

介绍

本实践将围绕实现一个日期类展开,旨在通过这一具体案例来教授类和对象的基础知识

我们将从定义类的基本结构开始,逐步添加属性和方法,以构建一个功能完备的日期类。

首先,我们需要明确日期类应该具备哪些基本属性。显然,一个日期应该包含年、月和日这三个关键信息。这些属性将用于表示日期的状态。

 

接下来,我们将为日期类定义一系列方法。这些方法将实现日期的各种操作,如设置日期、获取日期、计算两个日期之间的天数差、判断一个日期是否是闰年等。通过这些方法,日期类将具有自己的行为,能够根据需要进行各种计算和操作。

 

在实现日期类的过程中,我们还将涉及到一些面向对象编程的高级概念,如封装、继承和多态。封装将帮助我们隐藏类的内部实现细节,只暴露必要的接口给外部使用。虽然在这个简单的日期类示例中可能不会直接用到继承和多态,但了解这些概念将有助于我们更深入地理解面向对象编程的精髓。

一、类的设计

首先,我们需要设计一个日期类Date,包含年、月、日三个私有成员变量,并定义多个成员函数来实现日期的各种操作。

以下是类的声明部分:

#include<iostream>  
#include<stdbool.h>  
using namespace std;  class Date {  
public:  // 友元函数声明  friend ostream& operator<<(ostream& out, const Date& d);  friend istream& operator>>(istream& in, Date& d);  // 检查日期合法性  bool CheckDate() const;  // 获取某年某月的天数  int GetMonthDay(int year, int month) const;  // 构造函数  Date(int year = 1900, int month = 1, int day = 1);  // 拷贝构造函数  Date(const Date& d);  // 赋值运算符重载  Date& operator=(const Date& d);  // 析构函数  ~Date();  // 日期+=天数  Date& operator+=(int day);  // 日期+天数  Date operator+(int day) const;  // 日期-天数  Date operator-(int day) const;  // 日期-=天数  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::Date(int year, int month, int day) {  _year = year;  _month = month;  _day = day;  
}  Date::Date(const Date& d) {  _year = d._year;  _month = d._month;  _day = d._day;  
}  Date& Date::operator=(const Date& d) {  if (this != &d) {  _year = d._year;  _month = d._month;  _day = d._day;  }  return *this;  
}  Date::~Date() {}

解析:

  • 这四个成员函数都属于类的默认成员函数,是实现类的第一步
  • 日期类中除了构造函数需要自己单独实现之外,其他三个可使用编译器实现的默认成员函数即可。
  • 示例代码中在声明中给出了全缺省构造函数(全缺省构造函数是最稳妥的选择,可以适应各种情况),要注意带缺省参数声明和定义分离时,只在声明处给出缺省值
  • 更多默认成员函数知识可参考类和对象系列文章
  • C++指南_倔强的石头_的博客-CSDN博客

 

🍃友元函数:重载>>和<<

//运算符重载
ostream& operator<<(ostream& out,const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;//方便连续使用
}//运算符重载
istream& operator>>(istream& in, Date& d) 
{cout << "请输入年 月 日" << endl;while (1){in >> d._year >> d._month >> d._day;if (d.CheckDate())break;cout << "输入的日期非法,请重新输入!!!" << endl;}return in;
}

为什么要重载成友元函数(全局函数)而不是成员函数?

 void Date:: operator<<(ostream& out)//错误示范{out << this->_year << "-" << this->_month << "-" << this->_day;
}

如果我们重载成成员函数,运算符重载默认第一个参数为隐藏的this指针指向对象,第二个参数才是cout对象。那么想要使用cout打印对象内容时,就变成了下面这样的形式,这明显不符合我们的要求 

Date() << cout  ;

 只有改变两个对象的位置,让std::ostream对象的引用(std::ostream&)作为第一个参数才能解决,但this指针是隐藏的,我们无法修改。所有该问题只有通过重载成全局函数才能解决

 

为什么要有返回值?

对于输出运算符(<<),重载函数通常返回一个对std::ostream对象的引用(std::ostream&)。这样做允许我们进行链式操作,即将多个输出操作连接在一起,而不需要在每个操作后都调用std::endl或类似的分隔符。例如,std::cout << obj1 << obj2; 将首先输出obj1的状态,然后紧接着输出obj2的状态

 

 🍃日期合法性检查函数

bool Date::CheckDate()const
{if (_day > GetMonthDay(this->_year, this->_month) || _month > 12)return false;return true;
}

数据合法性检查是十分必要的 

 

🍃获取某年某月的天数

int Date:: GetMonthDay(int year, int month)const
{int getdays[] = { 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 getdays[month] + 1;//闰年二月天数+1}return getdays[month];
}
  • 考虑到天数的复杂性,每个月的天数都不一样,并且还存在闰年的2月份有29,逻辑复杂必须封装成函数实现
  • 将每个月的天数写在数组里,并空出第一个位置来,这样数组下标就能与月份一一对应
  • 获取月份的天数之后,再判断是否是2月以及是否是闰年再做进一步处理
  • 注意要先判断是否是2月,再判断是否是闰年,如果左侧不成立右侧就不会计算 

 

🍃比较运算符重载

// >运算符重载
bool Date::operator>(const Date& d)const
{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;//上述条件都没有执行,就返回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 || *this == d;//复用大于和等于
}// <运算符重载
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);
}
  • 这个函数通过逐步比较年份、月份和日期,实现了两个Date对象之间的“大于”比较。如果当前对象在任何一级比较中大于参数对象,就返回true;否则,返回false

  • 比较运算符中只需要实现一个大于或者小于运算符,和一个==运算符,其他的函数皆可复用代码

 

🍃 日期加减操作

// 日期+=天数
Date& Date::operator+=(int day)
{_day += day;int tmp = GetMonthDay(this->_year, this->_month);while (_day > tmp)//日期大于本月天数{_day -= tmp;//就减去本月天数++_month;//月份+1if (_month > 12)//月份大于12{++_year;//年份+1,月份置为1_month = 1;}tmp = GetMonthDay(this->_year, this->_month);}return *this;
}// 日期+天数
Date Date::operator+(int day)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;while (_day < 1){--_month;//向前借位if (_month == 0){--_year;_month = 12;}_day+= GetMonthDay(this->_year, this->_month);}return *this;
}

+=实现思路:

  1. 增加天数:将传入的天数day加到当前日期的天数_day上。
  2. 检查天数是否超出本月:使用GetMonthDay函数获取当前年份和月份的天数tmp。如果增加后的天数_day大于tmp,说明天数超出了当前月份的天数。
  3. 调整日期:如果天数超出,则减去当前月份的天数,并将月份_month加1。如果月份超过12(即一年结束),则将年份_year加1,并将月份重置为1。
  4. 重复检查:重复上述步骤,直到_day不大于当前月份的天数为止。
  5. 返回当前对象:返回对当前对象的引用,以支持链式操作。

+可以直接复用+=的代码 

-=实现思路:

  1. 减少天数:将传入的天数day从当前日期的天数_day中减去。
  2. 检查天数是否小于1:如果减少后的天数_day小于1,说明天数不足以满足当前月份,需要向前借位。
  3. 调整日期:如果天数小于1,则月份_month减1。如果月份减到0(即一年开始之前),则将年份_year减1,并将月份重置为12。然后,使用GetMonthDay函数获取新的月份的天数,并加到_day上。
  4. 重复检查:重复上述步骤,直到_day不小于1为止。
  5. 返回当前对象:返回对当前对象的引用,以支持链式操作。

-可以直接复用-=的代码

 

🍃自增自减操作

可以直接复用+=和-=的代码完成++和--函数

Date& Date::operator++() {  return *this += 1;  
}  Date Date::operator++(int) {  Date temp = *this;  ++(*this);  return temp;  
}  Date Date::operator--(int) {  Date temp = *this;  --(*this);  return temp;  
}  Date& Date::operator--() {  return *this -= 1;  
}

 

🍃日期差计算

// 日期-日期 返回天数
int Date::operator-(const Date& d)const
{int flag = 1;int count = 0;Date max = *this;//假设this大,d小Date min = d;if (max < min)//假设不对,则更改{max = d;min = *this;flag = -1;}while (min != max)//使用计数器计算{++min;++count;}return flag * count;
}

 思路解析:

  1. 变量初始化
    • flag:用于标记日期的先后顺序,以便确定最终结果的符号(正或负)。
    • count:用于累加两个日期之间的天数差。
    • maxmin:分别用于存储较大的日期和较小的日期,以便后续计算天数差。
  2. 假设与赋值
    • 假设当前对象(*this)表示的日期大于传入的日期d,因此将当前对象赋值给max,将d赋值给min
    • 如果假设不成立(即*this表示的日期小于d),则交换maxmin的赋值,并将flag设置为-1,表示最终的天数差应为负数。
  3. 计算天数差
    • 使用一个循环,每次循环将min表示的日期加1天,并累加count的值。
    • 循环继续,直到minmax表示的日期相等为止。此时,count的值即为两个日期之间的天数差。
  4. 返回结果
    • 根据flag的值,返回count-count作为两个日期之间的天数差。如果flag为1(即初始假设成立),则返回count;如果flag为-1(即初始假设不成立),则返回-count

 

  • 这种实现方式在日期相差较大时可能效率较低,因为它通过逐天累加来计算天数差
  • 但相对来说逻辑是比较简单的,直接计算的方法都比较复杂,涉及到不同的月份天数和闰年,因为天数的计算相对来说数据是比较小的,这些计算量在CPU面前还是小意思的

日期+日期没有实际意义,所有这里不进行运算符重载

 

结语

通过实现一个日期类,我们不仅能够掌握类和对象的基础知识,还能将这些知识应用于解决实际问题。同时,这一实践还将为我们后续学习更复杂的面向对象编程概念打下坚实的基础。

 

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

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

相关文章

基于32单片机的智能语音家居

一、主要功能介绍 以STM32F103C8T6单片机为控制核心&#xff0c;设计一款智能远程家电控制系统&#xff0c;该系统能实现如下功能&#xff1a; 1、可通过语音命令控制照明灯、空调、加热器、窗户及窗帘的开关&#xff1b; 2、可通过手机显示和控制照明灯、空调、窗户及窗帘的开…

hot100_54. 螺旋矩阵

hot100_54. 螺旋矩阵 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5] 示例 2&#xff1a; 输入&am…

HTML5实现好看的博客网站、通用大作业网页模板源码

HTML5实现好看的博客网站、通用大作业网页模板源码 前言一、设计来源1.1 主界面1.2 列表界面1.3 文章界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载结束语 HTML5实现好看的博客网站、通用大作业网页模板源码&#xff0c;博客网站源码&#xff0c;HTML模板源码&#xff0…

移动硬盘无法访问:全面解析、恢复方案与预防策略

描述移动硬盘无法访问现象 在日常的数据存储和传输过程中&#xff0c;移动硬盘无疑扮演着举足轻重的角色。然而&#xff0c;当移动硬盘突然无法访问时&#xff0c;这无疑给用户带来了巨大的困扰。想象一下&#xff0c;你急需从移动硬盘中调取一份重要文件&#xff0c;但系统却…

1-markdown转网页样式页面 --[制作网页模板] 【测试代码下载】

markdown转网页 将Markdown转换为带有样式的网页页面通常涉及以下几个步骤&#xff1a;首先&#xff0c;需要使用Markdown解析器将Markdown文本转换为HTML&#xff1b;其次&#xff0c;应用CSS样式来美化HTML内容。此外&#xff0c;还可以加入JavaScript以增加交互性。下面我将…

基于Centos 7系统的安全加固方案

创作不易&#xff0c;麻烦点个免费的赞和关注吧&#xff01; 声明&#xff01; 免责声明&#xff1a;本教程作者及相关参与人员对于任何直接或间接使用本教程内容而导致的任何形式的损失或损害&#xff0c;包括但不限于数据丢失、系统损坏、个人隐私泄露或经济损失等&#xf…

Angular由一个bug说起之十三:Cross Origin

跨域 想要了解跨域&#xff0c;首要要了解源 什么是源&#xff0c;源等于协议加域名加端口号 只有这三个都相同&#xff0c;才是同源&#xff0c;反之则是非同源。 比如下面这四个里&#xff0c;只有第4个是同源 而浏览器给服务器发送请求时&#xff0c;他们的源一样&#xff0…

nacos安装集群

本示例是安装在本地虚拟机linux环境。 &#xff08;1&#xff09;下载nacos https://download.csdn.net/download/lft18/90231054 &#xff08;2&#xff09;上传服务器并修改配置 放到/app/nacos目录下&#xff1a; 解压&#xff1a; tar -zxvf nacos-server-1.4.1.tar.…

taro转H5端踩坑

项目场景&#xff1a; 在利用taro进行多端开发时踩坑随记&#xff1a; 问题描述 在编译h5端的时候提示&#xff1a; Uncaught TypeError: (prevProps.className || prevProps.class || “”).split is not a function" return <ScrollView scrollY onScrollToLower{…

REVERSE-COMPETITION-CCSSSC-2025

REVERSE-COMPETITION-CCSSSC-2025 donntyouseeHappyLockkernel_traffic donntyousee elf64&#xff0c;ida反编译不太行&#xff0c;有花指令&#xff0c;直接调汇编 读输入 读输入前有条打印”plz input your flag”&#xff0c;肯定是在.init_array&#xff0c;确实有很多 …

海外招聘丨 弗拉瑞克商学院—博士研究员:智能家居技术业务和能源管理中的数据分析和人工智能

雇主简介 Vlerick 是一所领先的国际商学院……与众不同。是的&#xff0c;我们提供完全认可的世界一流教育课程&#xff0c;将理论知识和实践见解完美结合。是的&#xff0c;我们是一家领先的学术机构&#xff0c;拥有创新和独立研究的悠久传统。是的&#xff0c;我们拥有国际…

设计模式 行为型 策略模式(Strategy Pattern)与 常见技术框架应用 解析

策略模式&#xff08;Strategy Pattern&#xff09;核心思想是将算法的实现从使用该算法的类中分离出来&#xff0c;作为独立的对象&#xff0c;通过接口来定义算法家族&#xff0c;这样就可以很容易地改变或扩展算法。通过这种方式&#xff0c;可以避免在客户端代码中使用大量…

如何使用脚手架工具开始,快速搭建一个 Express 项目的基础架构

前言 将从如何使用脚手架工具开始&#xff0c;快速搭建一个 Express 项目的基础架构。接着&#xff0c;文章将详细讲解 Express 中间件的概念、分类以及如何有效地使用中间件来增强应用的功能和性能。最后&#xff0c;我们将讨论如何制定合理的接口规范&#xff0c;以确保 API …

《Opencv》基础操作详解(5)

接上篇&#xff1a;《Opencv》基础操作详解&#xff08;4&#xff09;-CSDN博客 目录 接上篇&#xff1a;《Opencv》基础操作详解&#xff08;4&#xff09;-CSDN博客 25、轮廓近似 简介 接口用法 参数说明 返回值 代码示例 结果展示 26、轮廓最小外接圆 简介 接口用…

Java虚拟机面试题:内存管理(上)

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

测试用例颗粒度说明

当我们在编写测试用例时&#xff0c;总是会遇到一个问题&#xff1a;如何确定测试用例的颗粒度&#xff1f;测试用例过于粗糙&#xff0c;可能无法全面覆盖系统的细节&#xff1b;而颗粒度过细&#xff0c;又会导致测试重复、冗余。掌握合适的颗粒度&#xff0c;不仅可以提高测…

【C++】深入解析二维数组初始化与越界问题

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;问题代码背景问题现象 &#x1f4af;初步分析与发现的问题1. 二维数组的初始化问题补充说明 2. 数组越界访问为什么数组越界问题没有直接报错&#xff1f; &#x1f4af;解…

Unity性能优化总结

目录 前言 移动端常见性能优化指标​编辑 包体大小优化 FPS CPU占用率 GPU占用率 内存 发热和耗电量 流量优化 前言 终于有时间了&#xff0c;我将在最近两个项目中进行优化的一些经验进行归纳总结以飨读者。因为我习惯用思维导图&#xff0c;所以归纳的内容主要以图来…

用QT实现 端口扫描工具1

安装在线QT&#xff0c;尽量是完整地自己进行安装&#xff0c;不然会少包 参考【保姆级图文教程】QT下载、安装、入门、配置VS Qt环境-CSDN博客 临时存储空间不够。 Windows系统通常会使用C盘来存储临时文件。 修改临时文件存储位置 打开系统属性&#xff1a; 右键点击“此电…

鸿蒙HarmonyOS开发:基于Swiper组件和自定义指示器实现多图片进度条轮播功能

文章目录 一、概述1、场景介绍2、技术选型 二、实现方案1、图片区域实现2、底部导航点设计3、手动切换 三、所有代码1、设置沉浸式2、外层Tabs效果3、ImageSwiper组件 四、效果展示 一、概述 在短视频平台上&#xff0c;经常可以见到多图片合集。它的特点是&#xff1a;由多张…