C++:日期类实现与运算符重载

目录

1. 日期类设计思路

2. 核心功能

2.1 构造与校验

2.2 日期运算

2.3 日期自增自减

2.4 各类比较运算符

2.5 输入输出流

3. 代码实现


1. 日期类设计思路

日期是一个看似简单但细节很多的类型。月有长短、闰年二月特殊、跨年进位……这些规则让日期类成为练习C++类设计的绝佳案例。

本文实现了一个完整的class Date,支持:

  • 日期合法性校验

  • 日期 ± 天数

  • 两个日期相减(得到天数差)

  • 前置/后置自增自减

  • 全部比较运算符

  • 流输入输出

2. 核心内容与功能

该日期类采用年、月、日三个成员变量实现。

成员变量:

class Date
{
private:
    int _year;
    int _month;
    int _day;
};

2.1 构造、析构与校验

默认构造函数:由于成员变量中不涉及资源空间的开辟,因此我们可以选择不写默认构造函数。但由于编译器对于内置类型的变量初始化是未定义的,为了严谨起见,这里手动实现一个全缺省的默认构造函数

// 全缺省的构造函数
// 声明
Date(int year = 1900, int month = 1, int day = 1);

// 定义
Date::Date(int year, int month, int day)
{
    _year = year;
    _month = month;
    _day = day;

    if(!CheckDate())
    {
        cout << "输入的日期非法!" << endl;
    }
}

注意:声明中需要具体写缺省值,定义中不能重复写,仅需写对应类型和形参。

拷贝构造函数:可以选择不写,还是由于不涉及资源空间开辟的原因。但是以练习为目的的话还是推荐自己手动实现一下的:

// 拷贝构造函数d2(d1)
// 声明
Date(const Date& d);

// 定义
Date::Date(const Date& d)
{
    _year = d._year;
    _month = d._month;
    _day = d._day;
}

说一下拷贝函数内部什么时候需要确认是否为自拷贝

如果涉及资源空间的开辟一定要检验拷贝不是自拷贝。

因为拷贝涉及对原有空间释放获取拷贝源头数据的操作,自拷贝会导致释放了自己的空间后又试图找到并拷贝这个空间,最终导致程序崩溃。

析构函数:可以选择不写,依旧是因为不涉及资源空间的开辟:

// 析构函数
// 声明
~Date();

// 定义
Date::~Date()
{
    _year = 0;
    _month = 0;
    _day = 0;
}

校验日期的合法性:

如何判断这个日期是否合法?

方便起见,我们抛开年份不谈(笔者不太清楚历史TvT),直接对月份和日期进行校验。

通过月份判断天数是否合法,容易想到可以先用数组实现一个获取天数的函数:把月份作为下标,通过访问下标获取天数,然后可以据此判断日期的合法性。

对于获取天数的函数,声明和定义都放在类内实现,默认是inline函数。这个函数经常使用且代码长度较短,符合inline函数标准。另外,用static把数组放在静态区可以减少不必要的数组创建和销毁,提高效率。

// 获取某年某月的天数
int GetMonthDay(int year, int month) const
{
    assert(month > 0 && month < 13);

    static int MonthDay[13] = {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 29;
    }
    return MonthDay[month];
}
// 判断日期合法
// 声明
bool CheckDate() const;

// 定义
bool Date::CheckDate() const
{
    if(_month < 1 || _month > 12)
    {
        return false;
    }

    if(_day < 1 || _day > GetMonthDay(_year, _month))
    {
        return false;
    }

    return true;
}

2.2 日期运算

在计算日期前,我们需要对以下运算符进行重载:

  •  + :日期加天数,返回新的日期,原日期不改变
  •  -  :日期减天数,返回新的日期,原日期不改变;日期减日期,返回相差的天数
  • += :日期加天数,返回新的原日期
  • - = :日期减天数,返回新的原日期

方便的是,只要实现了+=和-=,那么+和-可以复用这些操作符,大大减少了代码量。

计算方面,可以运用上之前获取天数的方法,通过类似加法进位,减法退位的方法计算日期。

注意:如果涉及原日期的修改,需要传引用返回;不需要修改,则需要传值返回。

Q:为什么不是+=和-=复用+和-?

A:因为+和-涉及到类对象的拷贝,会降低效率;而+=和-=是直接对对象的引用进行操作的,不会影响效率。

另外,如果日期是非法的,那么就不能进行运算了。因此在运算前记得检查一下日期的合法性😎

声明:

// 日期+=天数
Date& operator+=(int day);

// 日期-=天数
Date& operator-=(int day);

// 日期+天数
Date operator+(int day);

// 日期-天数
Date operator-(int day);

// 日期-日期 返回天数
int operator-(const Date& d);

定义:

// 日期+=天数
Date& Date::operator += (int day)
{
    if(!CheckDate())
    {
        cout << "日期非法,无法运算!" << endl;
        return *this;
    }

    if(day < 0)
    {
        return *this -= (-day);
    }

    _day += day;
    while(_day > GetMonthDay(_year, _month))
    {
        _day -= GetMonthDay(_year, _month);
        _month++;

        if(_month == 13)
        {
            _year++;
            _month = 1;
        }
    }

    return *this;
}

// 日期-=天数
Date& Date::operator -= (int day)
{
    if(!CheckDate())
    {
        cout << "日期非法,无法运算!" << endl;
        return *this;
    }

    if(day < 0)
    {
        return *this += (-day);
    }

    _day -= day;
    while(_day <= 0)
    {
        _day += GetMonthDay(_year, _month);
        _month--;

        if(_month == 0)
        {
            _year--;
            _month = 12;
        }
    }

    return *this;
}

// 日期+天数
Date Date::operator + (int day)
{
    Date tmp = *this;
    tmp += day;
    return tmp;
}

// 日期-天数
Date Date::operator - (int day)
{
    Date tmp = *this;
    tmp -= day;
    return tmp;
}

// 日期-日期 返回天数
int Date::operator - (const Date& d)
{
    int flag = 1;
    Date big = *this;
    Date small = d;

    if(*this < d)
    {
        big = d;
        small = *this;
        flag = -1;
    }

    int Nday = 0;
    while(small != big)
    {
        small++;
        Nday++;
    }

    return Nday * flag;
}

2.3 日期自增自减

自增自减涉及到前置运算符和后置运算符。由于运算符重载时第一个参数不需要显式传,自增自减又是一元运算符,会导致无法区分前置和后置。

因此C++规定,前置运算符重载时不传参,后置运算符重载需要传一个占位int类型来区分;

同时,由于前置先自增后使用的特性,可以直接传引用返回;后置自增则需要拷贝一次对象,对象自增后返回拷贝值,可以确保在使用后再自增,因此需要传值返回。

自减同理。

上述可见,在使用时更推荐前置自增自减,可以省去拷贝步骤,提高效率😎

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

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

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

2.4 各类比较运算符

有了日期运算的经验,我们可以在重载各类比较运算符时进行复用,比如手动实现了>和==的函数逻辑,就可以在>=,<=,<等复用了,省时省力🤩

不过至少要自己实现两个函数,切忌全部复用!否则就会出现你复用我,我复用你的无限递归情况,比如:

这里 >= 和 < 互相复用,就会陷入无限递归了TvT

// >运算符重载
bool Date::operator > (const Date& d)
{
    if(_year > d._year)
    {
        return true;
    }

    if(_year == d._year && _month > d._month)
    {
        return true;
    }

    if(_month == d._month && _day > d._day)
    {
        return true;
    }

    return false;
}

// ==运算符重载
bool Date::operator == (const Date& d)
{
    if(_year == d._year && _month == d._month && _day == d._day)
    {
        return true;
    }

    return false;
}

// >=运算符重载
bool Date::operator >= (const Date& d)
{
    return (!(*this < d));
}

// <运算符重载
bool Date::operator < (const Date& d)
{
    return (!((*this > d) || (*this == d)));
}

// <=运算符重载
bool Date::operator <= (const Date& d)
{
    return (!(*this > d));
}

// !=运算符重载
bool Date::operator != (const Date& d)
{
    return (!(*this == d));
}

2.5 输入输出流

为了方便日期的输入输出,这里还需要对运算符<<和>>进行重载。

但是,如果重载函数放在类内部,而类内部又默认第一个参数是类类型的引用,那么就会出现一个非常诡异的使用方式:

离谱的是,这种反直觉的设计是可以正常运行的,不过由于可读性极差,不支持链式调用等一系列问题,非常不推荐写成这种形式。因此,这里推荐一个更正规的写法:设置为全局函数

把运算符重载函数放在全局声明,并在类内部(位置不限)声明友元函数即可,这下就实现了和普通cin / cout一样的功能了:支持连续输入输出,使用起来和平时一样顺手🤓

//类内部友元函数声明
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
//输入输出(全局声明)
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
//输入输出(全局)
ostream& operator << (ostream& out, const Date& d)
{
    out << d._year << "年" << d._month << "月" << d._day << "日";
    return out;
}

istream& operator >> (istream& in, Date& d)
{
    cout << "请依次输入年月日:" << endl;
    while(1)
    {
        in >> d._year >> d._month >> d._day;
        if(!d.CheckDate())
        {
            cout << "输入日期" << d << "非法,请重新输入!" << endl;
        }
        else
        {
            break;
        }
    }

    return in;
}

3. 代码实现

以下是所有代码整合:

Date.h文件

#pragma once

#include<iostream>
#include<assert.h>
using namespace std;

class Date
{
    //友元函数声明
    friend ostream& operator<<(ostream& out, const Date& d);
    friend istream& operator>>(istream& in, Date& d);


public:

    // 获取某年某月的天数
    int GetMonthDay(int year, int month) const
    {
        assert(month > 0 && month < 13);

        static int MonthDay[13] = {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 29;
        }
        return MonthDay[month];
    }

    // 全缺省的构造函数
    Date(int year = 1900, int month = 1, int day = 1);

    // 拷贝构造函数
    // d2(d1)
    Date(const Date& d);

    // 析构函数
    ~Date();

    // 赋值运算符重载
    // d2 = d3 -> d2.operator=(&d2, d3)
    Date& operator=(const Date& d);

    //判断日期合法
    bool CheckDate() const;

    //打印日期
    void Print() const;

    // 日期+=天数
    Date& operator+=(int day);

    // 日期-=天数
    Date& operator-=(int day);

    // 日期+天数
    Date operator+(int day);

    // 日期-天数
    Date operator-(int day);

    // 前置++
    Date& operator++();

    // 后置++
    Date operator++(int);

    // 前置--
    Date& operator--();

    // 后置--
    Date operator--(int);

    // >运算符重载
    bool operator>(const Date& d);

    // ==运算符重载
    bool operator==(const Date& d);

    // >=运算符重载
    bool operator >= (const Date& d);

    // <运算符重载
    bool operator < (const Date& d);

    // <=运算符重载
    bool operator <= (const Date& d);

    // !=运算符重载
    bool operator != (const Date& d);

    // 日期-日期 返回天数
    int operator-(const Date& d);

private:

    int _year;
    int _month;
    int _day;
};

//输入输出(全局)
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);

Date.cpp文件

#include"Date.h"

// 全缺省的构造函数(参数只能给在声明中!)
Date::Date(int year, int month, int day)
{
    _year = year;
    _month = month;
    _day = day;

    if(!CheckDate())
    {
        cout << "输入的日期非法!" << endl;
    }
}

// 拷贝构造函数
// d2(d1)
Date::Date(const Date& d)
{
    _year = d._year;
    _month = d._month;
    _day = d._day;
}

// 析构函数
Date::~Date()
{
    _year = 0;
    _month = 0;
    _day = 0;
}

// 赋值运算符重载(如果开辟了资源需要先释放)
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
    _year = d._year;
    _month = d._month;
    _day = d._day;
    return *this;
}

//判断日期合法
bool Date::CheckDate() const
{
    if(_month < 1 || _month > 12)
    {
        return false;
    }

    if(_day < 1 || _day > GetMonthDay(_year, _month))
    {
        return false;
    }

    return true;
}

//打印日期
void Date::Print() const
{
    if(!CheckDate())
    {
        cout << "日期非法,无法打印!" << endl;
        return;
    }

    cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

// 日期+=天数
Date& Date::operator += (int day)
{
    if(!CheckDate())
    {
        cout << "日期非法,无法运算!" << endl;
        return *this;
    }

    if(day < 0)
    {
        return *this -= (-day);
    }

    _day += day;
    while(_day > GetMonthDay(_year, _month))
    {
        _day -= GetMonthDay(_year, _month);
        _month++;

        if(_month == 13)
        {
            _year++;
            _month = 1;
        }
    }

    return *this;
}

// 日期-=天数
Date& Date::operator -= (int day)
{
    if(!CheckDate())
    {
        cout << "日期非法,无法运算!" << endl;
        return *this;
    }

    if(day < 0)
    {
        return *this += (-day);
    }

    _day -= day;
    while(_day <= 0)
    {
        _day += GetMonthDay(_year, _month);
        _month--;

        if(_month == 0)
        {
            _year--;
            _month = 12;
        }
    }

    return *this;
}

// 日期+天数
Date Date::operator + (int day)
{
    Date tmp = *this;
    tmp += day;
    return tmp;
}

// 日期-天数
Date Date::operator - (int day)
{
    Date tmp = *this;
    tmp -= day;
    return tmp;
}

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

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

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

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

// >运算符重载
bool Date::operator > (const Date& d)
{
    if(_year > d._year)
    {
        return true;
    }

    if(_year == d._year && _month > d._month)
    {
        return true;
    }

    if(_month == d._month && _day > d._day)
    {
        return true;
    }

    return false;
}

// ==运算符重载
bool Date::operator == (const Date& d)
{
    if(_year == d._year && _month == d._month && _day == d._day)
    {
        return true;
    }

    return false;
}

// >=运算符重载
bool Date::operator >= (const Date& d)
{
    return (!(*this < d));
}

// <运算符重载
bool Date::operator < (const Date& d)
{
    return (!((*this > d) || (*this == d)));
}

// <=运算符重载
bool Date::operator <= (const Date& d)
{
    return (!(*this > d));
}

// !=运算符重载
bool Date::operator != (const Date& d)
{
    return (!(*this == d));
}

// 日期-日期 返回天数
int Date::operator - (const Date& d)
{
    int flag = 1;
    Date big = *this;
    Date small = d;

    if(*this < d)
    {
        big = d;
        small = *this;
        flag = -1;
    }

    int Nday = 0;
    while(small != big)
    {
        small++;
        Nday++;
    }

    return Nday * flag;
}

//输入输出(全局)
ostream& operator << (ostream& out, const Date& d)
{
    out << d._year << "年" << d._month << "月" << d._day << "日";
    return out;
}

istream& operator >> (istream& in, Date& d)
{
    cout << "请依次输入年月日:" << endl;
    while(1)
    {
        in >> d._year >> d._month >> d._day;
        if(!d.CheckDate())
        {
            cout << "输入日期" << d << "非法,请重新输入!" << endl;
        }
        else
        {
            break;
        }
    }

    return in;
}

如果对你有帮助,还请多多支持~[]~( ̄▽ ̄)~*我们下篇博客见

//封面是saki酱🥰

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值