• C++ Date 类的编写


    写在前面

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

    成果

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

    image-20220706150104866

    准备工作

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

    Date.h

    #include 
    #include 
    
    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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    Date.cpp

    #include "Date.h"
    
    Date::Date(int year, int month, int day)
    {
    }
    
    ~Date()
    {
    }
    
    Date(const Date& d)
    {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    test.cpp

    #include "Date.h"
    int main()
    {
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    构造函数

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

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

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

    判断平年 or 闰年

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

    bool Date::isLeap(int year)
    {
      assert(year > 0);
      return (year % 4 == 0 && year %100 != 0)
        || year % 400 == 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    判断天数是否合理

    我们都知道每一月分都有固定的天数,而且月份是肯定小于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];  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    我来解释一下我们定义数组的时候为什么用的是 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    析构函数 & 拷贝构造

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

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

    // 析构函数
    inline ~Date()
    {
      _year = 0;
      _month = 0;
      _day = 0;
    }
     
    // 拷贝构造
    inline Date(const Date& d)
    {
      _year = d._year;
      _month = d._month;
      _day = d._day;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    运算符重载

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

    逻辑运算符的重载

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

    重载 ==

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

    bool Date:: operator==(const Date& d) const
    {
        return _year == d._year
            && _month == d._month
            && _day == d._day;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    image-20220706165531482

    重载 >=

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

    bool Date::operator>=(const Date& d) const
    {
      return *this > d || *this == d;
    }
    
    • 1
    • 2
    • 3
    • 4

    image-20220706170227802

    重载 <

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

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

    bool Date::operator<(const Date& d) const
    {
      return !(*this >= d);
    }
    
    • 1
    • 2
    • 3
    • 4

    重载 <=

    小于等于的对立面是大于,我们已经实现了.

    bool Date::operator<=(const Date& d) const
    {
      return !(*this > d);
    }
    
    • 1
    • 2
    • 3
    • 4

    重载 !=

    这个更加简单,不等于不就是等于的对立面吗

    bool Date::operator!=(const Date& d) const
    {
      return !(*this ==  d);
    }
    
    • 1
    • 2
    • 3
    • 4

    算数运算符的重载

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

    重载 =

    等于的重载基本来说对于我们是没有任何问题的,但是有一点是需要我们注意的,无论是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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    重载 +

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

    那么我们该如何把这个逻辑给完善好呢?第一步,就是把给的天数直接加到_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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    重载+=

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

    Date& Date::operator+=(const int day)
    {
      //判断 是不是 小于 0
      if(day < 0)
      {
        return (*this) = (*this) - (-day);
      }
      return (*this) = (*this) + day;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    重载 -

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

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

    很荣幸,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;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    参数是日期

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

    还是先说下思路,我们想,如果一个较小的日期每次加一,直到加到和较大的日期完全一样,我们计算加的次数是不是就可以完成这个操作符的重载了.那么我们如何要得到更小的日期,是不是要比较,然后交换这里是不用的,我们用一个标志位.我们假设日期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;
      
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    重载-=

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

    Date& Date::operator-=(const int day)
    {
      if(day < 0)
      {
        return(*this) = (*this)+(-day);
      }
      return (*this) = (*this) - day;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    重载++

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

    前置

    前置是不需要带参数的.

    Date& Date::operator++()
    {
      *this += 1;
      return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    后置

    需要带一个int类型的参数

    Date Date::operator++(int day)
    {
      Date ret(*this);
      *this += 1;
      return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    重载 –

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

    前置

    不带参数

    Date& Date::operator--()
    {
      *this -= 1;
      return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    后置

    带上参数

    Date Date:: operator--(int day)
    {
      Date ret(*this);
      *this -= 1;
      return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    重载流提取 & 流插入

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

    >>

    std::istream& operator>>(std::istream& in, Date& d)
    {
    	in >> d._year >> d._month >> d._day;
    	return in;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    <<

    std::ostream& operator<<(std::ostream& out, Date& d)
    {
    	out << d._year << "-" << d._month << "-" << d._day;
    	return out;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    干货分享:有什么软件可以让照片动起来?
    seata框架
    ElasticSearch--查看健康状态(health)的方法(API)
    深入理解函数式编程(上)
    java特种兵读书笔记(4-4)——java通信之tomcat对IO请求的处理
    JavaScript问题清单与经验
    cmake链接ffmpeg静态库的方法,及报错答解
    12.OpenFeign 实例(springcloud)
    十三、队列的特性
    【数据结构】链式二叉树(超详细)
  • 原文地址:https://blog.csdn.net/m0_61334618/article/details/125718958