C++ Date 类的编写

写在前面

我们今天写一个Date类作为C++初始类的结尾,这里涉及到的知识,里面有很多运算符的重载,包括权限的问题,我们都已经分享过了,所以大家不用担心,这里算是一个总结吧.我这里用的的是Linux环境,主要是锻炼自己的能力.

成果

我们要完成一个什么样的Date类呢,大家可以搜一下时间计算器,我们就完成他们的功能,比如说加减300天或者看看两个日期之间的天数,这就是我们要完成的任务.

image-20220706150104866

准备工作

这里我们用三个文件来写,分别是Date.h,Date.cpp,test.cpp.我们先把初始工作给做好了,我们先把框架各搭出来,后面要的功能一一补足.

Date.h

#include <iostream>
#include <assert.h>

using std::cout;
using std::endl;

class Date
{
public:
    
  Date(int year = 1900, int month = 1, int day = 1);
  //析构函数
  ~Date();
  // 拷贝构造
  Date(const Date& d);
private:
	int _year;
	int _month;
	int _day;
};

Date.cpp

#include "Date.h"

Date::Date(int year, int month, int day)
{
}

~Date()
{
}

Date(const Date& d)
{
}

test.cpp

#include "Date.h"
int main()
{
    
    return 0;
}

构造函数

我们先来写一下日期类的构造函数,本来这是没有多少问题的,但是我们知道一个月的天数是有限制的,而且还有闰年于平年之分,这就考虑的有点难度了,但是这要符合我们的客观规律.我们先来把思路捋顺.

第一点 我们把年月份给对象,其中肯定都是大于0的整数,这个是毋庸置疑的,第二步,我们要判断这一年是平年还是闰年,第三步,要判断给的月数的对应的天数是否合理.

我们分别用方法来完成自己的要求.

判断平年 or 闰年

这个很简单,记得去类里面声明这个方法.

bool Date::isLeap(int year)
{
  assert(year > 0);
  return (year % 4 == 0 && year %100 != 0)
    || year % 400 == 0;
}

判断天数是否合理

我们都知道每一月分都有固定的天数,而且月份是肯定小于13的,

int Date::isLegitimate(int year,int month)
{
  
  static int monthDay[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
  assert(month > 0 && month < 13);
  assert(day > 0);
  //判断闰年 && 而且 月份是 2 月
  if(isLeap(year) && month == 2)
  {
    return 29;
  }
  return monthDay[month];  
}

我来解释一下我们定义数组的时候为什么用的是 static ? 我们知道,这是一个函数,也就是说函数栈帧会在结束后销毁,但是我们可能多次调用这个函数,为了避免多次开辟和销毁该数组的空间,这里直接用static修饰得了.

写好构造函数

到这里我们已经写好了的构造函数,没必要在说其他了.

Date::Date(int year = 1900, int month = 1, int day = 1)
{
   if(year > 0 && month > 0 && month <13 && day <= isLegitimate(year,month))
    {
        _year = year;
        _month = month;
        _day = day;
    }
    else 
    {
        // 这里应该抛出一个异常 
        // 现在我还不太会 暂时用 assert代替
        assert(NULL);
    }
}

析构函数 & 拷贝构造

说实话,这两个函数我们不用写,主要是我们没有使用动态开辟空间,都是一些基本的内容,编译器生成的已经完全够了,这里大家看看就行了.

由于这两个函数都不大,这里就声明成内联函数函数吧,在类内实现.

// 析构函数
inline ~Date()
{
  _year = 0;
  _month = 0;
  _day = 0;
}
 
// 拷贝构造
inline Date(const Date& d)
{
  _year = d._year;
  _month = d._month;
  _day = d._day;
}

运算符重载

某种意义上,运算符重载才是我们今天的大头,我们不是重载所有的运算符,而是要重载那些符合我们逻辑的,例如一个日期加上一个日期就没有什么意义,这里就不会重载它.

逻辑运算符的重载

这个基本的逻辑运算符都有意义,这里我们一一给大家重载出来,同时也验证一下.

重载 ==

这个很好写,如果日期的年月日都相等,那么这两个日期一定相等.

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

image-20220706164559795

重载 >

如果一个日期的年数比较大,那么它一定更大,如果相等,就比较月份,如果月份还相等,那就比较天数,这就是这个的原理.

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

image-20220706165531482

重载 >=

只要我们完成了上面的两个,后面就可以直接调用它们了,避免重复造轮子.

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

image-20220706170227802

重载 <

从这里开始,我就不演示结果了,都是和之前的一样.

我们知道,小于的对立面就是大于大于等于

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

算数运算符的重载

上面的都挺简单的,这里算数运算符我们要重载的有加法,等于,减法,前置和后置++…有一定的额难度,尤其是加法和减法.

重载 =

等于的重载基本来说对于我们是没有任何问题的,但是有一点是需要我们注意的,无论是C语言还是C++都是支持连等的,也就是a = b = c,那就意味者我们们重载等于的时候是要有返回值的.

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

重载 +

说实话,加号还是比较简单的,我们首先要把给的天数判断一下,假如要是小于零,就去调用重载的减号,后面我会实现减法的,这里先考虑好.

那么我们该如何把这个逻辑给完善好呢?第一步,就是把给的天数直接加到_day上面,如果还没有超过当前月最大的天数,我们就直接返回这个日期就可以了,否则就把日期减去当前月最大的天数,当前月加一,注意,这里要明白,如果当前月是12月,我们直接把月份置为1,年加上一,这里要注意一点东西 ,我们不修改原来的日期

我们可以分为三种情况,每一个我都列出来,本质上是一个循环.

  1. 2022-2-1 + 12 _day = 13 < 2月份的最大值,返回 2022-2-13
  2. 2022-4-30 + 7 _day = 37 > 4月份最大值, _day-=30 月份+1 = 5,我们发现 _day = 7 < 5月份的最大值,循环结束
  3. 2022-12-31 + 2 _day = 33 > 12月份最大值, _day-=31,月份是12,直接置为1, _year加1,继续循环,判断 _day = 2是不是满足循环条件
Date Date::operator+(const int day) const
{
  //先判断  day 是否是 小于零
  if(day < 0)
  {
    return (*this)-(-day);
  }
  // 第一步  来个第三方 不要修改原来的
  Date ret(*this); // 这是拷贝构造
  ret._day += day;
  while(ret._day > ret.isLegitimate(ret._year,ret._month))
  {
    ret._day -= ret.isLegitimate(ret._year,ret._month);
    
    if(ret._month == 12)
    {
      ret._month = 1;
      ret._year += 1;
    }
    else 
    {
      ret._month += 1;
    }
  }

  return ret;
}

重载+=

我们直接复用+和=就可以了,这里没什么可以分享的.

Date& Date::operator+=(const int day)
{
  //判断 是不是 小于 0
  if(day < 0)
  {
    return (*this) = (*this) - (-day);
  }
  return (*this) = (*this) + day;
}

重载 -

我们们这里要重载加号的的话,需要分为两种共请情况

  • 参数 是 天数
  • 参数 是 日期

很荣幸,C++是支持重载的,我们也按照步骤来.

参数是天数

这个就是计算一个日期减去多少天得到另一个日期,也是比较简单的,我们要考虑一些之情况,这里小于零的情况就不解释了,主要看我们的思路是什么.

我们首先把_day减去day,判断是不是小于0,小于的话,就从上个月的日期天数加到 _day上,直到它它大于0,这里要注意的是,如果我们的月份恰好是12,那么上一个月是1月份,并且年也要减1

Date Date::operator-(const int day) const
{
  // day 的大小 
  if(day < 0)
  {
    return (*this) + (-day);
  }
  Date ret(*this);
  ret._day -= day;

  //开始判断  ret._day 
  while(ret._day <= 0)
  {
    // 找上一个月的
    if(ret._month == 1)
    {
      ret._month = 12;
      ret._year -= 1;
    }
    else 
    {
      ret._month -= 1;
    }
    int days = ret.isLegitimate(ret._year,ret._month);
    ret._day += days;
  }
  return ret;
} 
参数是日期

这个更加简单的,我们计算的是两个日期之间差的天数,我们可以复用前面的方法.

还是先说下思路,我们想,如果一个较小的日期每次加一,直到加到和较大的日期完全一样,我们计算加的次数是不是就可以完成这个操作符的重载了.那么我们如何要得到更小的日期,是不是要比较,然后交换这里是不用的,我们用一个标志位.我们假设日期A比日期B小,标志位flag = 1,如果不成立,我们把flag编程-1,随即我们用日期A加上flag,进行循环.

int Date:: operator-(const Date& d)
{
  // 给一个 数  来计数
  int count = 0;
  int flag = 1;
  // 不能修改原来的,这里用一个第三方
  Date ret(*this);
  if(ret > d)
  {
    flag = -1;
  }
  while(ret != d)
  {
    ret += flag;
    count++;
  }
  return flag*count;
  
}

重载-=

这个我们复用减号就可以了,就不解释了.

Date& Date::operator-=(const int day)
{
  if(day < 0)
  {
    return(*this) = (*this)+(-day);
  }
  return (*this) = (*this) - day;
}

重载++

我们这就不解释前置和后置的区别了,上一个博客分享过了,直接开始吧.

前置

前置是不需要带参数的.

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

后置

需要带一个int类型的参数

Date Date::operator++(int day)
{
  Date ret(*this);
  *this += 1;
  return ret;
}

重载 –

既然我们都把减法给重载了,那这我们直接复用就可以了

前置

不带参数

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

后置

带上参数

Date Date:: operator--(int day)
{
  Date ret(*this);
  *this -= 1;
  return ret;
}

重载流提取 & 流插入

这原理我们已经分享过了,这里就不加赘述了,记得使用友元

>>

std::istream& operator>>(std::istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

<<

std::ostream& operator<<(std::ostream& out, Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day;
	return out;
}
  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
c++/qt写的项目,可供自己学习,项目都经测试过,真实可靠,请放心使用。Qt支持 Windows、Linux/Unix、Mac OS X、Android、BlackBerry、QNX等多种平台,并为这些不同的平台提供了统一的开发环境。 面向对象 C++是完全面向对象的,这一点和Objective-c等在开发很相似。而Qt又是基于C++一种语言的扩展,大家都知道C++ 有快速、简易、面向对象等很多优点,所以Qt自然也继承者C++这些的优点。 Qt良好的封装机制使得Qt的模块化程度非常高,可重用性较好,对用户开发来货是非常方便的。Qt提供一种为signals/slots(信号和槽) 的安全型来替代callback,使得各个元件之间的协同工作变得十分简单。 丰富的API Qt包括多达 250 个以上的 C++ ,还提供基于模板的 collections, serialization, file, I/Odevice, directory management, date/time 。甚至还包括正则表达式的处理功能。 支持 2D/3D 图形渲染,支持 OpenGL。 大量的开发文档。 XML支持 Webkit 引擎的集成,可以实现本地界面与Web内容的无缝集成, 但是真正使得 Qt 在自由软件界的众多 Widgets (如 Lesstif,Gtk,EZWGL,Xforms,fltk 等等)中脱颖而出的还是基于 Qt 的重量级软件 KDE。 信号和槽机制 Qt提供了信号和槽机制用于完成见面操作的响应,是完成任意两个Qt对象之通信机制。其中,信号会在某个特定情况或动作下被触动,槽是等同于接受并处理信号的函数。 为什么方法不是直接调用的。中间用到 Signal 和槽机制不是多此一举? 其实在我们生活也是一样,老板级别的好说话,老板给助理分派任务也好说话,但是助理给老板分任务,可想而知会有什么后果,在以前的统治阶层肯定不允许这样的事发生。所以在分层思想中,我们所调用的函数也是这样的,上层可以调用下层和同一层的函数,下层函数不可以调用上层函数,否则程序的层次性会被打破,导致结构错综复杂,难以维护和管理。 那么怎样才能做到向上管理呢,有任务分配给老板怎么办? 老板会设立一个机构,也就是一个函数,用无限循环来查询助理的状态,如果助理真的有事情,这个机构就把这消息拿到老板来处理。但是这种处理方式显得有些复杂,我们想要的简单明了的方式是,如果助理有事件发生,可以直接调用老板函数处理。 说了这么多其实就是想说,信号和槽的最大优势在于,它完善了程序分层的思想,可以在不改变程序的层次性的情况下,完成由下层到上层的调用。在下层发出一个 Signal,这时上层与其想关联的 Slot 函数就会响应。
呈上头文件部分信息 注释的也很详细的 COPYRIGHT NOTICE Copyright c 2009 华中科技大学tickTick Group (版权声明) All rights reserved @file SerialPort h @brief 串口通信头文件 本文件完成串口通信的声明 @version 1 0 @author 卢俊 @E mail:lujun hust@gmail com @date 2010 03 19 修订说明: #ifndef SERIALPORT H #define SERIALPORT H #include <Windows h> 串口通信实现了对串口的基本操作 例如监听发到指定串口的数据 发送指定数据到串口 class CSerialPort { public: CSerialPort void ; CSerialPort void ; public: 初始化串口函数 @param: UINT portNo 串口编号 默认值为1 即COM1 注意 尽量不要大于9 @param: UINT baud 波特率 默认为9600 @param: char parity 是否进行奇偶校验 "Y"表示需要奇偶校验 "N"表示不需要奇偶校验 @param: UINT databits 数据位的个数 默认值为8个数据位 @param: UINT stopsbits 停止位使用格式 默认值为1 @param: DWORD dwCommEvents 默认为EV RXCHAR 即只要收发任意一个字符 则产生一个事件 @return: bool 初始化是否成功 @note: 在使用其他本提供的函数前 请先调用本函数进行串口的初始化       n本函数提供了一些常用的串口参数设置 若需要自行设置详细的DCB参数 可使用重载函数 n本串口析构时会自动关闭串口 无需额外执行关闭串口 @see: bool InitPort UINT portNo 1 UINT baud CBR 9600 char parity "N" UINT databits 8 UINT stopsbits 1 DWORD dwCommEvents EV RXCHAR ; 串口初始化函数 本函数提供直接根据DCB参数设置串口参数 @param: UINT portNo @param: const LPDCB & plDCB @return: bool 初始化是否成功 @note: 本函数提供用户自定义地串口初始化参数 @see: bool InitPort UINT portNo const LPDCB& plDCB ; 开启监听线程 本监听线程完成对串口数据的监听 并将接收到的数据打印到屏幕输出 @return: bool 操作是否成功 @note: 当线程已经处于开启状态时 返回flase @see: bool OpenListenThread ; 关闭监听线程 @return: bool 操作是否成功 @note: 调用本函数后 监听串口的线程将会被关闭 @see: bool CloseListenTread ; 向串口写数据 将缓冲区中的数据写入到串口 @param: unsigned char pData 指向需要写入串口的数据缓冲区 @param: unsigned int length 需要写入的数据长度 @return: bool 操作是否成功 @note: length不要大于pData所指向缓冲区的大小 @see: bool WriteData unsigned char pData unsigned int length ; 获取串口缓冲区中的字节数 @return: UINT 操作是否成功 @note: 当串口缓冲区中无数据时 返回0 @see: UINT GetBytesInCOM ; 读取串口接收缓冲区中一个字节的数据 @param: char & cRecved 存放读取数据的字符变量 @return: bool 读取是否成功 @note: @see: bool ReadChar char &cRecved ; private: 打开串口 @param: UINT portNo 串口设备号 @return: bool 打开是否成功 @note: @see: bool openPort UINT portNo ; 关闭串口 @return: void 操作是否成功 @note: @see: void ClosePort ; 串口监听线程 监听来自串口的数据和信息 @param: void pParam 线程参数 @return: UINT WINAPI 线程返回值 @note: @see: static UINT WINAPI ListenThread void pParam ; private: 串口句柄 HANDLE m hComm; 线程退出标志变量 static bool s bExit; 线程句柄 volatile HANDLE m hListenThread; 同步互斥 临界区保护 CRITICAL SECTION m csCommunicationSync; < 互斥操作串口 }; #endif SERIALPORT H ">呈上头文件部分信息 注释的也很详细的 COPYRIGHT NOTICE Copyright c 2009 华中科技大学tickTick Group (版权声明) All rights reserved @file SerialPort h @brief [更多]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玄鸟轩墨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值