【C++】:日期类实现

简介: 【C++】:日期类实现

前言:

承接上篇的类和对象本期我们来实现一下日期类,顺便再来回顾一下构造函数和运算符的重载,关于基本的运算符重载就不做过多的解释,之前没提到过的会在这里重点解释。

1. 基本构造

日期类的实现我们采用分文件来进行实现:

在头文件 Date.h中实现日期类的基本框架

在源文件 Date.cpp中实现框架逻辑

在源文件 Test.cpp中实现测试逻辑

日期类的基本创建只需要写出构造函数和打印函数即可,因为日期类中没有资源的创建与释放,所以编译器自动生成的析构函数拷贝构造以及&运算符重载足够使用。

头文件 :Date.h

//日期类
class Date
{
public:
  //不涉及内部数据的修改的函数可以加上const修饰
  //拷贝构造函数(全缺省)
  Date(int year = 1949, int month = 10, int day = 1);
  //打印函数
  void Print() const;
private:
  int _year;  //年
  int _month; //月
  int _day;   //日
};

源文件:Date.cpp

//拷贝构造函数(全缺省)
Date::Date(int year, int month, int day)
{
  _year = year;
  _month = month;
  _day = day;
}
//打印函数
void Date::Print() const
{
  cout << _year << "/" << _month << "/" << _day << endl;
}

2. 基础运算符重载

基础运算符的重载包括==、!=、<、<=、>、>=,这几个运算符重载比较简单,在这里就不做解析。

头文件:Date.h

//日期类
class Date
{
public:
  //不涉及内部数据的修改的函数可以加上const修饰
  //拷贝构造函数(全缺省)
  Date(int year = 1949, int month = 10, int day = 1);
  //打印函数
  void Print() 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;
  //>=运算符重载
  bool operator>=(const Date& d) const;
private:
  int _year;  //年
  int _month; //月
  int _day;   //日
};

源文件 :Date.cpp

//==运算符重载
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);
}
//<运算符重载
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;
  }
  else
  {
    return false;
  }
}
//<=运算符重载
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);
}

3. 进阶运算符重载

基础的运算符重载只能满足一部分的需求,还需要对日期的加减。

3.1 日期+天数

一个日期+一个天数即可得到另外的一个日期,这里就存在一个天数满了进位月份,月份满了进位年份,那么就需要一个另外的函数来获取某一月的天数。

获取天数函数:

//获取某一个月的天数
int Date::GetMonthDay(int year, int month) const
{
  const static int MonthArray[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 MonthArray[month];
}

首先判断是否为闰年,然后根据月份返回相对应的天数。

日期+天数代码:

头文件:Date.h

//日期类
class Date
{
public:
    //拷贝构造函数(全缺省)
  Date(int year = 1949, int month = 10, int day = 1);
  //获取某一个月的天数
  int GetMonthDay(int year, int month) const;
  //+=运算符重载
  Date& operator+=(int day);
  //+运算符重载
  Date operator+(int day) const;
private:
  int _year;  //年
  int _month; //月
  int _day;   //日
};

源文件:Date.cpp

//获取某一个月的天数
int Date::GetMonthDay(int year, int month) const
{
  const static int MonthArray[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 MonthArray[month];
}
//+=运算符重载
Date& Date::operator+=(int 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) const
{
  //构造一个新的Date
  Date tmp(*this);
  tmp += day;
  return tmp;
}

3.2 日期-天数

头文件:Date.h

//日期类
class Date
{
public:
  //拷贝构造函数(全缺省)
  Date(int year = 1949, int month = 10, int day = 1);
  //获取某一个月的天数
  int GetMonthDay(int year, int month) const;
  //-=运算符重载
  Date& operator-=(int day);
  //-运算符重载
  Date operator-(int day) const;
private:
  int _year;  //年
  int _month; //月
  int _day;   //日
};

源文件:Date.cpp

//获取某一个月的天数
int Date::GetMonthDay(int year, int month) const
{
  const static int MonthArray[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 MonthArray[month];
}
//-=运算符重载
Date& Date::operator-=(int day)
{
  _day -= day;
  while (_day <= 0)
  {
    //向月份借位
    --_month;
    if (_month == 0)
    {
      //向年份借位
      --_year;
      _month = 12;
    }
    _day += GetMonthDay(_year, _month);
  }
  return *this;
}
//-运算符重载
Date Date::operator-(int day) const
{
  Date tmp(*this);
  tmp -= day;
  return tmp;
}

3.3 日期+、-天数的优化版本

上面实现的这两个运算符重载还是有一定的缺陷的,万一有的人在+天数时传递负值就会出现问题,如果有人在-天数的时候传递负值也是会出现bug,那么就需要再次进行优化。

当使用日期+天数时传递负值直接复用-=操作

当使用日期-天数时传递负值直接复用+=操作

//+=运算符重载
Date& Date::operator+=(int day)
{
  //负值天数操作
  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 (day < 0)
  {
    return *this += (-day);
  }
  _day -= day;
  while (_day <= 0)
  {
    //向月份借位
    --_month;
    if (_month == 0)
    {
      //向年份借位
      --_year;
      _month = 12;
    }
    _day += GetMonthDay(_year, _month);
  }
  return *this;
}

3.4 前置++、后置++、前置--、后置--

上篇的文章中提到过前置和后置的区别就是后置++多了一个int参数

头文件:Date.h

//日期类
class Date
{
public:
  //前置++运算符重载
  Date& operator++();
  //后置++运算符重载
  Date operator++(int) const;
  //前置--运算符重载
  Date& operator--();
  //后置--运算符重载
  Date operator--(int) const;
private:
  int _year;  //年
  int _month; //月
  int _day;   //日
};

源文件:Date.cpp

//前置++运算符重载
Date& Date::operator++()
{
  *this += 1;
  return *this;
}
//后置++运算符重载
Date Date::operator++(int) const
{
  Date tmp(*this);
  tmp += 1;
  return tmp;
}
//前置--运算符重载
Date& Date::operator--()
{
  *this -= 1;
  return *this;
}
//后置--运算符重载
Date Date::operator--(int) const
{
  Date tmp(*this);
  tmp -= 1;
  return tmp;
}

3.5 日期与日期相差天数

日期与日期相差天数也是使用-的运算符重载,日期-日期这里可以直接减,也可以使用两个日期同时减去2000年1月1日,然后根据差值计算天数,需要注意的是日期-日期是会出现负数的。还有一种比较简单的方法计数法:设置一个标志值记录正负,然后记录两个日期当中最大的一个日期,然后设置一个计数值,将小日期加1,同时也将计数值加1,直到小日期加到和大日期相等,那么此时这个计数值就是它们之间相差的日期。

源文件:Date.cpp

//日期-日期
int Date::operator-(const Date& d) const
{
  Date dmax = *this;
  Date dmin = d;
  int flag = 1;  //记录正负
  //找出较大的日期
  if (*this < d)
  {
    dmax = d;
    dmin = *this;
    flag = -1;
  }
  //设置计数值
  int coutN = 0;
  while (dmin != dmax)
  {
    ++dmin;
    ++coutN;
  }
  //将差值再*记录值
  return coutN * flag;
}

4. 流提取、流插入运算符重载

如果我们使用cout来直接打印日期类的话是不能打印的,同样的使用cin来对日期类输入也是不支持的,因为日期类是不属于内置类型的,如果我们也要像内置类型一样使用cin和cout就需要对>>(流提取)<<(流插入)进行重载

cin是一个istream类中的对象,内置类型能使用流提取的原因就是库里面将内置类型都进行重载了。

cout是一个ostream类中的对象

4.1 流插入

我们要实现自定义类型的流插入就需要用到库里面的流插入来进行辅助:

//头文件:Date.h
//日期类
class Date
{
public: 
  //拷贝构造函数(全缺省)
  Date(int year = 1949, int month = 10, int day = 1);
  //打印函数
  //流插入
  void operator<<(ostream& out);
private:
  int _year;  //年
  int _month; //月
  int _day;   //日
};
//源文件:Date.cpp
//流插入
void Date::operator<<(ostream& out)
{
  out << _year << "/" << _month << "/" << _day << endl;
}

如果这样子写的话会出现一个问题:我们正常调用会调用不了:

类成员函数的第一个参数是隐藏的this指针,所以正常调用的话跟重载的顺序不匹配:

实现为这样非要调用的话只需要将顺序换一下即可:

但是这样子调用又不符合日常调用习惯,所以我们需要将两个参数的位置调换,但是类成员函数的第一个函数是默认隐藏的,所以需要调换的话就需要写为全局运算符重载函数,但是设为全局又存在一个新的问题,类中的私有函数在类外面不能访问,这里有两种方法:

1. 使用单独的函数将年份、月份、天数都分别保存起来直接使用。

2. 将流插入运算符重载设置为友元函数

在这里我们使用第二种方法:

//头文件:Date.h
//日期类
class Date
{
  //友元声明
  friend void operator<<(ostream& out, const Date& d);
public:
  //拷贝构造函数(全缺省)
  Date(int year = 1949, int month = 10, int day = 1);
    //...
private:
  int _year;  //年
  int _month; //月
  int _day;   //日
};
//流插入
void operator<<(ostream& out, const Date& dt);
//源文件:Date.cpp
//流插入
void operator<<(ostream& out, const Date& d)
{
  out << d._year << "/" << d._month << "/" << d._day << endl;
}

这样写的话还是存在一个小小的问题:每次输出的话只能一个一个日期类进行输出,不能一次性输出多个:

这个问题的主要原因是前面的输出d1是一个正常的运算符重载,而这个运算符重载的返回值又是void,而接下来的运算符重载不认识void类型,所以需要返回一个ostream类型才能完成下面的输出。

完整的优化代码:

头文件:Date.h

//日期类
class Date
{
  //友元声明
  friend ostream& operator<<(ostream& out, const Date& d);
public:
  //不涉及内部数据的修改的函数可以加上const修饰
  //拷贝构造函数(全缺省)
  Date(int year = 1949, int month = 10, int day = 1);
  //...
private:
  int _year;  //年
  int _month; //月
  int _day;   //日
};
//流插入
ostream& operator<<(ostream& out, const Date& d);

源文件:Date.cpp

//流插入
ostream& operator<<(ostream& out, const Date& d)
{
  out << d._year << "/" << d._month << "/" << d._day << endl;
  return out;
}

4.2 流提取

cin是一个istream类中的对象,所以我们可以借助istream来实现对于日期类的输入:

头文件:Date.h

//日期类
class Date
{
  //友元声明
  friend ostream& operator<<(ostream& out, const Date& d);
  friend istream& operator>>(istream& in, Date& d);
public:
  //不涉及内部数据的修改的函数可以加上const修饰
  //拷贝构造函数(全缺省)
  Date(int year = 1949, int month = 10, int day = 1);
    //...
private:
  int _year;  //年
  int _month; //月
  int _day;   //日
};
//流插入
ostream& operator<<(ostream& out, const Date& d);
//流提取
istream& operator>>(istream& in, Date& d);

源文件:Dtae.cpp

//流插入
ostream& operator<<(ostream& out, const Date& d)
{
  out << d._year << "/" << d._month << "/" << d._day << endl;
  return out;
}
//流提取
istream& operator>>(istream& in, Date& d)
{
  in >> d._year >> d._month >> d._day;
  return in;
}

5. 完整代码

头文件:Date.h

#pragma once
#include <iostream>
using namespace std;
//日期类
class Date
{
  //友元声明
  friend ostream& operator<<(ostream& out, const Date& d);
  friend istream& operator>>(istream& in, Date& d);
public:
  //不涉及内部数据的修改的函数可以加上const修饰
  //拷贝构造函数(全缺省)
  Date(int year = 1949, int month = 10, int day = 1);
  //打印函数
  void Print() const;
  //获取某一个月的天数
  int GetMonthDay(int year, int month) 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;
  //>=运算符重载
  bool operator>=(const Date& d) const;
  //+=运算符重载
  Date& operator+=(int day);
  //+运算符重载
  Date operator+(int day) const;
  //-=运算符重载
  Date& operator-=(int day);
  //-运算符重载
  Date operator-(int day) const;
  //前置++运算符重载
  Date& operator++();
  //后置++运算符重载
  Date operator++(int) const;
  //前置--运算符重载
  Date& operator--();
  //后置--运算符重载
  Date operator--(int) const;
  //日期-日期
  int operator-(const Date& d) const;
private:
  int _year;  //年
  int _month; //月
  int _day;   //日
};
//流插入
ostream& operator<<(ostream& out, const Date& d);
//流提取
istream& operator>>(istream& in, Date& d);

源文件:Date.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
//拷贝构造函数(全缺省)
Date::Date(int year, int month, int day)
{
  _year = year;
  _month = month;
  _day = day;
}
//打印函数
void Date::Print() const
{
  cout << _year << "/" << _month << "/" << _day << endl;
}
//==运算符重载
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);
}
//<运算符重载
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;
  }
  else
  {
    return false;
  }
}
//<=运算符重载
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);
}
//获取某一个月的天数
int Date::GetMonthDay(int year, int month) const
{
  const static int MonthArray[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 MonthArray[month];
}
//+=运算符重载
Date& Date::operator+=(int day)
{
  //负值天数操作
  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) const
{
  //构造一个新的Date
  Date tmp(*this);
  tmp += day;
  return tmp;
}
//-=运算符重载
Date& Date::operator-=(int day)
{
  //负值天数操作
  if (day < 0)
  {
    return *this += (-day);
  }
  _day -= day;
  while (_day <= 0)
  {
    //向月份借位
    --_month;
    if (_month == 0)
    {
      //向年份借位
      --_year;
      _month = 12;
    }
    _day += GetMonthDay(_year, _month);
  }
  return *this;
}
//-运算符重载
Date Date::operator-(int day) const
{
  Date tmp(*this);
  tmp -= day;
  return tmp;
}
//前置++运算符重载
Date& Date::operator++()
{
  *this += 1;
  return *this;
}
//后置++运算符重载
Date Date::operator++(int) const
{
  Date tmp(*this);
  tmp += 1;
  return tmp;
}
//前置--运算符重载
Date& Date::operator--()
{
  *this -= 1;
  return *this;
}
//后置--运算符重载
Date Date::operator--(int) const
{
  Date tmp(*this);
  tmp -= 1;
  return tmp;
}
//日期-日期
int Date::operator-(const Date& d) const
{
  Date dmax = *this;
  Date dmin = d;
  int flag = 1;  //记录正负
  //找出较大的日期
  if (*this < d)
  {
    dmax = d;
    dmin = *this;
    flag = -1;
  }
  //设置计数值
  int coutN = 0;
  while (dmin != dmax)
  {
    ++dmin;
    ++coutN;
  }
  //将差值再*记录值
  return coutN * flag;
}
//流插入
ostream& operator<<(ostream& out, const Date& d)
{
  out << d._year << "/" << d._month << "/" << d._day << endl;
  return out;
}
//流提取
istream& operator>>(istream& in, Date& d)
{
  in >> d._year >> d._month >> d._day;
  return in;
}

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!

目录
相关文章
|
4天前
|
设计模式 安全 算法
【C++入门到精通】特殊类的设计 | 单例模式 [ C++入门 ]
【C++入门到精通】特殊类的设计 | 单例模式 [ C++入门 ]
15 0
|
6天前
|
C语言 C++
【C++】string类(常用接口)
【C++】string类(常用接口)
15 1
|
3天前
|
编译器 C++
【C++】继续学习 string类 吧
首先不得不说的是由于历史原因,string的接口多达130多个,简直冗杂… 所以学习过程中,我们只需要选取常用的,好用的来进行使用即可(有种垃圾堆里翻美食的感觉)
7 1
|
3天前
|
算法 安全 程序员
【C++】STL学习之旅——初识STL,认识string类
现在我正式开始学习STL,这让我期待好久了,一想到不用手撕链表,手搓堆栈,心里非常爽
10 0
|
3天前
|
存储 安全 测试技术
【C++】string学习 — 手搓string类项目
C++ 的 string 类是 C++ 标准库中提供的一个用于处理字符串的类。它在 C++ 的历史中扮演了重要的角色,为字符串处理提供了更加方便、高效的方法。
13 0
【C++】string学习 — 手搓string类项目
|
4天前
|
Java C++ Python
【C++从练气到飞升】06---重识类和对象(二)
【C++从练气到飞升】06---重识类和对象(二)
|
4天前
|
编译器 C++
【C++从练气到飞升】06---重识类和对象(一)
【C++从练气到飞升】06---重识类和对象(一)
|
4天前
|
存储 编译器 C语言
【C++从练气到飞升】02---初识类与对象
【C++从练气到飞升】02---初识类与对象
|
4天前
|
设计模式 算法 编译器
【C++入门到精通】特殊类的设计 |只能在堆 ( 栈 ) 上创建对象的类 |禁止拷贝和继承的类 [ C++入门 ]
【C++入门到精通】特殊类的设计 |只能在堆 ( 栈 ) 上创建对象的类 |禁止拷贝和继承的类 [ C++入门 ]
9 0
|
4天前
|
算法 安全 调度
【C++入门到精通】 线程库 | thread类 C++11 [ C++入门 ]
【C++入门到精通】 线程库 | thread类 C++11 [ C++入门 ]
14 1