手撕日期类的实现【C++版】

前言

接上篇我们学习了C++ 的六个默认成员函数以后呢,相信我们都有一定的收获,那本节内容呢,小编将带大家手撕完整的一个日期类的实现,从而来加深大家对上节内容(6个默认成员函数)的认识。那么话不多说,我们直接开始。

一、日期类的实现的所有功能预览图

在这里插入图片描述
说明:日期类的实现小编将分文件编写代码(更规范);date.h 放函数的声明,
date.cpp 放函数的实现,test.cpp 放测试代码

二、 date.h(放函数的声明)

//date.h
#include<iostream>
using namespace std;

class Date
{
public:
    //构造函数
    Date(int year = 0,int month = 1;int day = 1);
    Date(const Date& d); //拷贝构造  //其实拷贝构造也可以不自己写,用编译器生成的也可以
    
    //打印日期
    void Print()const;
    
    //日期+天数
    Date operator+(int day)const;
    
    //日期+=天数
    Date& operator+=(int day);
    
    //日期-天数
    Date operator-(int day)const;
    
    //日期-=天数
    Date& operator-=(int day);
    
    //日期类的六个大小比较关系
    bool operator>(const Date&d)const;   //>运算符重载
    /*若是这样比(d1 > d2),实际上这里编译器会转换成
    bool operator>(const Date* this,const Date&d);*/
    //这里只举一个例子,下面的类似
    
    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++();
    
    //后置++
    Date operator++(int);
    
    //前置--
    Date& operator++();
    
    //后置--
    Date operator++(int);
    
    //日期-日期
    int operator-const Date& d)const;
    
    //日期+日期没啥意义,这里不写
    
    //由于日期类的析构函数不用清理什么资源,所有这里可以不写,直接用编译器生成的就好 
     
private:
    int _year;
    int _month;
    int _day;
}

说明:以上用 const 修饰的成员函数,函数体内都没有对 this 指针指向的对象进行修改,所以加上 const ,避免误操作或者调用的时候编译出错(权限问题,上一篇笔记有)。

三、date.cpp (函数的实现)

3.1 构造函数

  • 构造函数之前需要检查一下日期的合法性,只有日期是合法的,才能进入构造函数体进行构造操作。
#include" Date.h"
//构造函数

//为了方便,写一个函数获取某年某月的天数
/*由于这个函数后续会被多次调用,考虑到调用函数函数栈帧的建立和销毁
是有消耗的,这里我们直接用 inline内联函数将其展开*/
inline int Date:: GetMonthday(int year,int month)
{
    //这里数组开13个,把月份对上,下面代码可读性更好。
    /*由于每次进来都要重新创建数组,而数组是不用变,所以这里直接
    用 static 静态修饰的更好,这样每次进来就都用同一个数组了,减少消耗。*/
    static int  Monthdays[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31}//是二月且是闰年返回29天
    if( month == 2 
    && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
    {
        return 29;
    }
    return Monthdays[month];
}

/*当函数声明和定义分离时,在声明处标注了缺省参数
定义时就不能标注缺省参数了)*/
Date::Date(int year,int month;int day)
{
    //检查一下日期是否合法,合法才构造
    if( year >= 0 && month >= 1 && month <= 12
      && day >=1 && day <= GetMonthday(year,month))
    {
        _year = year;  
        _month = month;
        _day = day;
    }
    else
    {
        //也可以抛异常,抛异常更好
        cout << "你输入的是非法日期“ << endl;
    }
}

//拷贝构造也可以写一下
Date::Date(const Date& d)
    {   
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

3.2 打印日期

//打印日期
void Date:: Print()const
{
       cout << _year << "-" << _month << "-" << _day << endl;
} 

3.3 日期+天数

日期+ 天数返回值还是一个日期,那日期 + 一个天数是怎么加的呢?首先加完之后的日期我们要保证它的合法性,举个例子 : 假如 2020 年 12 月 31 天,加一天是多少呢?很明显,如果结果是 2020.12.32 是不合法的,那这个时候呢我们就应该减去12月的天数,然后往月数进位(也就是日数 - 31,月数 + 1);这个时候,很明显月数也是满的,那我们就把月数置成1(如果没满12 月的话,月数+1 即可,满了才置成1),然后再往年进位(年数+1);这样的话结果就是 2021.1.1 ;这个日期是合法的,有了这个例子,我们就有大概思路了,来看代码:

注意:我们返回的是加完之后的值,但是这里对象本身的值是不变的,就像 b = c + 1; c + 1 的返回值是 c + 1;但是 c 的值是没有改变的,所以这里我们要拷贝构造一个临时对象用于返回,同时也可以用 const 对函数进行修饰,防止函数内部修改了 this 指针指向的对象。

Date Date::operator+(int day)const
{
/*  
    Date ret(*this); //拷贝构造
    ret._day += day;
    while (ret._day > GetMonthday(ret._year,ret._month)) //不合法就一直减和进位
    {
        //如果日期的天数不合法,就需要往月进位
        ret._day -= GetMonthday(ret._year,ret._month);
        ret._month++;
        //检查月数,月满往年进位
        if( ret._month == 13)
        {
            ret._year++;
            ret._month = 1;
         }
    }
    return ret;   */
    
    /*有了下面的 +=,我们就可以用来复用实现 +,
    (复用让代码灵活性更好,遇到改动大的
    也不用花费很多时间去改,所以建议用复用)*/
    
    Date ret(*this); //拷贝构造
    ret += day;  //直接复用下面 -=;
    return ret;
}

3.4 日期+=天数

日期+=天数 的逻辑和上面 + 的一样的,但是有一个区别是:+= 完以后这里对象本身的值是改变的,返回的值就是对象本身的值,所以这里不需要拷贝构造一个临时的变量,而是直接返回 *this 即可,函数也不用用 const 修饰,因为对象本身也改变了。

小补充:这里的 *this 出了这个函数的作用域以后这个对象还在,所以可以用引用返回,减少拷贝,而上面 日期 + 天数的那个是创建了一个临时对象用于返回,出了函数的作用域临时对象就销毁了,所以不能用引用返回,不然返回的是一块销毁的空间,就会有问题。

Date& Date::operator+=(int day)
{ 
    /*这里我们有一个问题,如果 += 的是一个负数,结果就会出现
    负的日期,但是 += 一个负数,可以看作 -= 一个正数;
    所以这里我们也要判断一下,解决这个细节问题*/
    if(day < 0)
    {
        //复用 operator-=
        return *this -= -day;
    }
    
    _day += day;
    while (_day > GetMonthday(_year,_month)) //不合法就一直减和进位
    {
        //如果日期的天数不合法,就需要往月进位
        _day -= GetMonthday(_year,_month);
        ++_month;
        //检查月数,月满往年进位
        if( _month == 13)
        {
            _year++;
            _month = 1;
         }
    }
    return *this;
}

3.5 日期-天数

日期 - 天数返回值也是一个日期,那日期 - 一个天数是怎么搞的呢?首先减完之后的日期我们也要保证它的合法性。举个例子:假如 2020 年 4 月 15 日,减去 20 天是多少呢?如果结果是 2020.4.-5 。显然是不合法的,日期是没有负数的,那这里怎么办呢?很明显,我们这里不够减,那我们往上个月去借天数(也就是将上个月的天数加上,-5 + 31),然后月数 - 1,也就是 2020.3.26 。这样不就好了吗,同样的,假如我们借到了一月还不够借,也可以往上一年去借,这时候只需要把 上一年 12 月的天数加上,把月数置成12,年数 - 1 即可。有了这个思路,我们看代码实现吧。

这里注意的地方和 + 是一样的,返回的值是减完的值,但是对象本身不变。其他地方都是类似的。

Date Date::operator-(int day)const
{/*
    Date ret(*this); //拷贝构造
    ret._day -= day;
    while (ret._day < 1) //不合法就一直减和借
    {
        --ret._month;
        //检查月数,月减到0往年借
        if( ret._month == 0)
        {
            --ret._year;
            ret._month = 12;
         }
        ret._day += GetMonthday(ret._year,ret._month);
    }
    return ret;  */
    
    /*有了下面的 -=,我们就可以用来复用实现 -,
    (复用让代码灵活性更好,遇到改动大的也不用花费很多时间去改,所以建议用复用)*/
    
    Date ret(*this); //拷贝构造
    ret -= day;  //直接复用下面 -=;
    return ret;
}

3.6 日期-=天数

日期 -=天数 的逻辑和上面 - 的也是一样的,但是区别也是:-= 完以后这里对象本身的值是改变的,返回的值就是对象本身的值,所以这里不需要拷贝构造一个临时的变量,而是直接返回 *this 即可,函数也不用用 const 修饰,因为对象本身也改变了。

注意的和 += 也是类似的。

Date& Date::operator-=(int day)
{
    //这个地方和上面同理
    if(day < 0)
    {
        //复用 operator+=
        return *this += -day;
    }
    
    _day -= day;
    while (_day < 1) //不合法就一直减和借
    {
        --_month;
        //检查月数,月减到0往年借
        if( _month == 0)
        {
            --_year;
            _month = 12;
         }
        _day += GetMonthday(_year,_month); //加上上个月天数
    }
    return *this;
}

3.7 日期类的六个大小比较关系

>运算符重载

要判断大于是这样的,先判断年,年大于则大于,要是年相等,再判断月,年相等月大于则大于,如果年月都相等,再判断天,年月相等天大于则大于。(大于则返回 true,否则返回 false ),代码如下

bool Date:: operator>(const Date& d)const
{ 
    if(_year > d._year)
        return true;
    else if(_year == d._year && _month > d._month)
        return true;
    else if(_year == d._year && _month == d._month && _day > d._day)
        return true;
    
    return 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
{ 
    if(_year > d._year)
        return true;
    else if(_year == d._year && _month > d._month)
        return true;
    else if(_year == d._year && _month == d._month && _day >= d._day)
        return true;
    
    return false;
} */
//我们来看看复用的效果
bool Date:: operator>=(const Date& d)const
{ 
    return *this > d || *this == d;  //复用上面的来实现
}

解释:举个例子,假如是 d1 >= d2,编译器会把 d1 >= d2 转换成 d1.operator>=(&d1,d2); d1 的地址就被传给了上面函数隐含的this指针,*this 就相当于 d1。d 就相当于d2。

<运算符重载

这里 的小于是一样的 ,也可以复用上面的,小于 即是 大于等于 逻辑上的取反,我们来看代码:

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

3.8 前置++

前置++ 返回的是 + 一天之后的值,加完以后呢对象本身会改变。所以这里返回 *this就可以。

Date& Date:: operator++()
{
    *this += 1;
    return *this;  //返回加之后的值
}

3.9 后置++

后置++ 返回的是 + 一天之前的值,加完以后呢对象本身也会改变。所以这里返回 一个临时对象。

//为了构成重载,后置++这里加了一个形参int,但是只是为了区分前置和后置,使用的时候不用传参
Date Date:: operator++(int)  
{
    Date tmp(*this);
    *this += 1;
    return tmp; //返回加之前的值
}

3.10 前置–

前置-- 返回的是 - 一天之后的值,减完以后呢对象本身会改变。所以这里返回 *this就可以。

Date& Date:: operator--()
{
    *this -= 1;
    return *this;  //返回减之后的值
}

3.11 后置–

后置-- 返回的是 - 一天之前的值,减完以后呢对象本身也会改变。所以这里返回 一个临时对象。

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

3.12 日期-日期

日期 - 日期,就是计算两个日期相差的天数。我们发现直接相减是比较困难的,考虑的东西很多。这里呢,我们提供一个很巧妙的思路:让较小的日期的天数一直 加 1;直到跟较大的日期相等就停止,那这个过程中小日期加的总天数就是这两个日期相差的天数的绝对值。如果第一个日期是大于第二个日期的,我们就返回相差天数的正值,否则就返回相差天数的负值。我们来看代码实现:

int Date::operator-(const Date& d)const
{
    //找到较小的日期去++
    Date max = *this;
    Date min = d;
    int flag = 1; //第一个日期大,第二个日期小,返回正数
    if( *this < d)
    {
        max = d;
        min = *this;
        flag = -1//第二个日期大,第一个日期小,返回负值
    }

    int n = 0; //记录加的总天数
    
    while(min != max)
    {
        ++min; //不等于较大日期时,小日期一直加 1
        ++n;
    }
    
    return flag*n;
}
        

3.13 小知识

小知识:内联函数不能声明和定义分离。在声明和定义分离时,不能将声明和定义分离的函数加上 inline ,否则链接的时候找不到地址,会导致报错(因为内联函数在调用的地方展开,是没有地址的,当调用时会先去找到声明,这时候展开声明是没有内容的(内容在定义那里),但是又找不到定义的地址,导致链接不上,就会报错)。但是在声明和定义没有分离时可以加上。
另外:或者把函数体直接写到类里面。写在类里面的函数都默认是内联函数。

四、test.cpp(测试代码)

#include"date.h"

int main()
{
// 构造函数,日期的合法检查,打印函数的测试
    Date d1;
    d1.Print();
    Date d2(2024,9,1);
    d2.Print();
    Date d3(2024,3,32);
    d3.Print(); 

//日期+天数 的测试代码
    Date d4 = d2 + 10;
    d4.Print();
    Date d5 = d4 + 100;
    d5.Print();
    Date d6 = d5 + 1000;
    d6.Print();
    Date d7 = d4 + -100;
    d7.Print();

//日期+=天数 的测试代码
    Date d8 = d2 += 100;
    d2.Print();
    d8.print(); 
    
// 日期-天数、日期-=天数 和上面类似。

//日期类的六个大小比较关系的测试代码
    cout << (d1 > d2) << endl; //d1 > d2 实际上编译器会转成 d1.operator>(&d1,d2);
    cout << (d1 == d2) << endl;
    cout << (d1 >= d2) << endl;
    cout << (d1 < d2) << endl;
    cout << (d1 <= d2) << endl;
    cout << (d1 != d2) << endl;
   
   //前置++,后置++ 的测试
    ++d1; 
    d1.Print();
    d1++;
    d1.Print();
    
    //前置--,后置-- 类似

    //日期-日期测试代码
    Date s1(2024,9,1);
    Date s2(2020,1,23);
    cout << s1 - s2 << endl;
    cout << s2 - s1 << endl;
    
    return 0;
}

总结

本节内容是一个日期类的完整实现,是对上一节内容六个默认成员函数的一个应用,以及旨在加深对上节内容的理解。如果大家有所收获,点个赞吧!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值