如何实现一个完整的日期类?

简介: Tip关于内联函数:在类内定义的函数,如果代码量少的话,编译器会酌将其转换成内联函数,这样会在调用的地方直接展开,能够提高效率。在这个日期类中,提高的效率不是很大,所以在本文将日期类的成员函数的声明和定义分离了。如果想写成内联,需要直接在类里面定义。(内联的声明和定义不能分离)

🍖前言

通过类和对象的基本学习,我们可以实现一个完整的日期类。本文探讨日期类如何实现。

Tip

关于内联函数:在类内定义的函数,如果代码量少的话,编译器会酌将其转换成内联函数,这样会在调用的地方直接展开,能够提高效率。

在这个日期类中,提高的效率不是很大,所以在本文将日期类的成员函数的声明和定义分离了。如果想写成内联,需要直接在类里面定义。(内联的声明和定义不能分离)

日期类的六大默认成员函数:

日期类的成员变量如下:

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

一、🍖构造函数

由于日期类的成员变量只有年月日,且无其他的动态资源的申请,所以构造函数可直接实现:

  // 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
  if (year < 0 || year>9999)
  {
    perror("error year!");
    exit(-1);
  }
  if (month < 0 || month > 12)
  {
    perror("error month!");
    exit(-1);
  }
  static int DaysGet[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;
  }
  else
  {
    return DaysGet[month];
  }
}
        //构造函数
Date::Date(int year = 1900, int month = 1, int day = 1)
  {
    if (year < 0 || year>9999 )
    {
      perror("error year!");
      exit(-1);
    }
    if (month < 0 || month > 12)
    {
      perror("error month!");
      exit(-1);
    }
    if (day < 0 || day > GetMonthDay(year, month))
    {
      perror("error day!");
      exit(-1);
    }
    _year = year;
    _month = month;
    _day = day;
  }

Tip:

GetMonthDay() 函数是获取某年某月的天数,比如2023年5月,返回31天。

细节点1.

GteMonthDay的数组写成static是为了提高效率,因为GetMonthDay函数需要不断地调用,所以这个数组只需要创建一次即可,不需要重复创建。

细节点2.

判断闰年部分,最开始先要判断月份是否为2月,(因为闰年只跟2月有关)再判断是否为闰年即可提高效率。

二、🍖拷贝构造函数

日期类不需要动态申请空间资源,所以这里的拷贝构造函数可写可不写,因为如果我们不写,编译器生成的拷贝构造函数会自己完成内置类型的浅拷贝。

 Date::Date(const Date& d)
{
  this->_year = d._year;
  this->_month = d._month;
  this->_day = d._day;
}

三、🍖析构函数

由于日期类没有动态资源空间的申请,不需要实现析构函数。

四、🍖日期类的赋值运算符重载

Date& Date::operator=(const Date& d)
{
  //防止自己给自己赋值,可以判断一下
  //注意&d的&是取地址的意思
  if (this != &d)
  {
    this->_year = d._year;
    this->_month = d._month;
    this->_day = d._day;
  }
  return *this;
}

Tip:

1.赋值运算符重载的返回类型是Date&,为了达到连续赋值的效果,且能提高效率。

2.参数设置为const Date&,是因为赋值前后右值未发生改变,使用const修饰更安全,且传引用可以提高效率,减少调用构造函数或拷贝构造函数的次数。

五、🍖取地址运算符重载

返回对象的地址即可。(在日期类没有特别大的意义)

const Date* Date::operator&()
{
  return this;
}

六、🍖const取地址运算符重载

返回对象的地址即可。(在日期类没有特别大的意义)

const Date* Date::operator&() const
{
  return this;
}

Tip:

取地址运算符重载和const取地址运算符重载区别在于this指针是否被const修饰,

一个是 Datethis, 一个是 const Datethis,它们构成函数重载。

日期类的关系操作符重载

在日期类中,只要实现

1.>运算符重载和==运算符重载

或者

2.<运算符重载和==运算符重载

即可完成其他的关系运算符重载。

一、🍖>运算符重载

1.先判断年是否大于

2.如果年大于,再判断是否月大于

3.如果年和月都大于,再判断日是否大于

以上三种情况均返回真,其余情况返回假

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

二、🍖==运算符重载

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

三、🍖>=运算符重载

此时可以复用 > 运算符重载和 == 运算符重载了。

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

六、🍖!=运算符重载

!=运算符重载又可以复用 ==运算符重载,只需要取反方向即可。

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

日期类和天数的操作

一、🍖日期+=天数

日期+=天数,就是让日期本身变了。

比如:2023年1月1日+10天后,原来的日期变成了2023年1月11日。

返回原来的对象,但是对象的成员变量已被修改。

具体实现:

给定一个天数,首先让对象的天数加上,如果对象的天数大于当月的天数,就让对象的天数给减掉当月的天数,然后让月份进一位,然后判断月份是否大于12,如果月份大于12,让年进一位,同时让月份回到1月,如此循环。

最后返回*this,即返回自身。

//+=就是让自己变了
Date& Date::operator+=(int day)
{
  //如果天数<0,意思就是
  if (day < 0)
  {
    return *this -= -day;
  }
  _day += day;
  while (_day > GetMonthDay(_year, _month))
  {
    _day -= GetMonthDay(_year, _month);
    _month += 1;
    if (_month > 12)
    {
      _year += 1;
      _month = 1;
    }
  }
  return *this;
}

Tip:

需要注意的点:如果日期小于0,相当于+=一个小于0的数,即 -= 一个大于0的数,可以复用下面实现的-=运算符重载。

二、🍖日期+天数

直接复用+=即可。

但是要注意的是,日期+天数返回的是一个新的对象,不是返回原来的对象。

所以需要在函数内部新建一个对象,该新的对象调用它拷贝构造函数拷贝原来的对象,+=天数后返回新对象。

Date Date::operator+(int day)
{
  //可以复用 +=
  Date tmp(*this);
  tmp.operator+=(day);
  return tmp;
}

三、🍖日期-=天数

整体实现方法与+=类似,但需要注意的是:

-=时,天数应该与上一个月的天数进行比较,因为-的是上个月的天数。

首先将对象的日期减掉天数,如果对象日期小于0,那就需要从上一个月来借用天数了。

回到上一个月先需要–_month,才可以回到上个月,–对象的月份后,如果月份小于1了,就需要从年借一年,–对象的年,然后让月份 = 12,如此循环。

Date& Date::operator-=(int day)
{
  if (day < 0)
  {
    return *this += -day;
  }
  _day -= day;
  while (_day < 1)
  {
    --_month;
    if (_month < 1)
    {
      --_year;
      _month = 12;
    }
    _day += GetMonthDay(_year, _month);
  }
  return *this;
}

Tip:

需要注意的点:如果日期小于0,相当于-=一个小于0的数,即 +=一个大于0的数,可以复用+=运算符重载。

四、🍖日期-天数

复用-=即可,与日期+天数类似,返回的是一个新的对象,不改变原对象。

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

五、🍖日期-日期

日期-日期的思想在于,先假设被减数是大的日期,减数是小的日期,如果假设不成立,那就交换一下顺序。

如果是小-大,那么返回日期的负数,表示回退多少多少天。

计算天数方法:从小的日期开始,不断累加,直到加到和大的日期相等即可。

在此期间会调用++运算符重载,在下面会实现。

//日期-日期
int Date::operator-(const Date& d)
{
  //小-大返回负数,大-小返回正数
  Date max = *this;
  Date min = d;
  int flag = 1;
  //如果假设错误,交换一下
  if (max < min)
  {
    max = d;
    min = *this;
    flag = -1;
  }
  int days = 0;//计算天数差
  while (min != max) //!=更高效, <还要比年月日
  {
    ++days;
    ++min;
  }
  return  flag * days;
}

日期类++和–操作

一、🍖前置++

复用+=运算符重载即可。

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

二、🍖后置++

注意前置++和后置++是构成函数重载的,为了区分,我们给后置++一个参数类型,该参数不需要用到,所以给不给参数名都可以。

一般来说,尽量使用前置++,因为后置++返回的是++之前的对象,需要新建立一个对象,返回再返回该对象,新建立对象会调用拷贝构造函数,返回该对象又会给临时空间调用拷贝构造函数,效率较低。

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

三、🍖前置–

–操作的解释与++操作相似。

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

四、🍖后置–

–操作的解释与++操作相似。

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

整体代码(Date.h 和Date.cpp)

Date.h文件

class Date
{
  //成员函数如果不放在public里面,默认就是私有的
public:
  // 全缺省的构造函数
  Date(int year = 1900, int month = 1, int day = 1);
  // 获取某年某月的天数
  int GetMonthDay(int year, int month);
  void Print()
  {
    printf("%d %d %d\n", _year, _month, _day);
  }
  //本质上是:
  //void Print(Date* const this)
  //d1.Print()   本质上是:
  //d1.Print(&d1);
  // 拷贝构造函数
  // d2(d1)
//必须传引用,如果传值调用,会导致无限递归
  Date(const Date& d);
  //取地址运算符重载
  const Date* operator&();
  //const取地址运算符重载
  const Date* operator&() const;
  //函数声明
  //赋值运算符重载
  Date& operator=(const Date& d);
  Date& operator+=(int day);
  Date operator+(int day);
  Date operator-(int day);
  Date& operator-=(int day);
  //前置++
  Date& operator++();
  //后置++
  Date operator++(int);
  //后置--
  Date operator--(int);
  //前置--
  Date& operator--();
  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);
  //如果不设置成友元函数
  // d << cout ,特别别扭,因为this指针是指向d的,传参穿的是cout流插入符号
  ostream& operator<<(ostream& out);
  //这两个构成重载
  const Date* operator&();
  const Date* operator&() const;
private:
  int _year;
  int _month;
  int _day;
};

Date.cpp文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
  if (year < 0 || year>9999)
  {
    perror("error year!");
    exit(-1);
  }
  if (month < 0 || month > 12)
  {
    perror("error month!");
    exit(-1);
  }
  static int DaysGet[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;
  }
  else
  {
    return DaysGet[month];
  }
}
//构造函数
Date::Date(int year = 1900, int month = 1, int day = 1)
{
  if (year < 0 || year>9999)
  {
    perror("error year!");
    exit(-1);
  }
  if (month < 0 || month > 12)
  {
    perror("error month!");
    exit(-1);
  }
  if (day < 0 || day > GetMonthDay(year, month))
  {
    perror("error day!");
    exit(-1);
  }
  _year = year;
  _month = month;
  _day = day;
}
//拷贝构造
Date::Date(const Date& d)
{
  this->_year = d._year;
  this->_month = d._month;
  this->_day = d._day;
}
// 析构函数
//日期类不需要析构
//~Date();
// 赋值运算符重载
Date& Date::operator=(const Date& d)
{
  //防止自己给自己赋值,可以判断一下
  //注意&d的&是取地址的意思
  if (this != &d)
  {
    this->_year = d._year;
    this->_month = d._month;
    this->_day = d._day;
  }
  return *this;
}
//取地址运算符重载
const Date* Date::operator&()
{
  return this;
}
//const取地址运算符重载
const Date* Date::operator&() const
{
  return this;
}
// 日期+=天数
//+=就是让自己变了
Date& Date::operator+=(int day)
{
  if (day < 0)
  {
    return *this -= -day;
  }
  _day += day;
  while (_day > GetMonthDay(_year, _month))
  {
    _day -= GetMonthDay(_year, _month);
    _month += 1;
    if (_month > 12)
    {
      _year += 1;
      _month = 1;
    }
  }
  return *this;
}
// 日期+天数
Date Date::operator+(int day)
{
  //可以复用 +=
  Date tmp(*this);
  tmp.operator+=(day);
  return tmp;
}
// 日期-=天数
Date& Date::operator-=(int day)
{
  if (day < 0)
  {
    return *this += -day;
  }
  _day -= day;
  while (_day < 1)
  {
    --_month;
    if (_month < 1)
    {
      --_year;
      _month = 12;
    }
    _day += GetMonthDay(_year, _month);
  }
  return *this;
}
// 日期-天数
Date Date::operator-(int day)
{
  //可以复用 -=
  Date tmp(*this);
  tmp.operator-=(day);
  return tmp;
}
//日期-日期
int Date::operator-(const Date& d)
{
  //小-大返回负数,大-小返回正数
  Date max = *this;
  Date min = d;
  int flag = 1;
  //如果假设错误,交换一下
  if (max < min)
  {
    max = d;
    min = *this;
    flag = -1;
  }
  int days = 0;//计算天数差
  while (min != max) //!=更高效, <还要比年月日
  {
    ++days;
    ++min;
  }
  return  flag * days;
}
// >运算符重载
//实现一个大于和一个等于就可以完成其他所有的操作了
bool Date::operator>(const Date& d)
{
  if (_year > d._year)
  {
    return true;
  }
  if (_year == d._year && (_month > d._month))
  {
    return true;
  }
  if (_year == d._year && (_month > d._month) && _day > d._day)
  {
    return true;
  }
  return false;
}
// ==运算符重载
bool Date::operator==(const Date& d)
{
  return _year == d._year && _month == d._month && _day == d._day;
}
// >=运算符重载
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);
}
// !=运算符重载
bool Date::operator != (const Date& d)
{
  return !(*this == d);
}
// 前置++
Date& Date::operator++()
{
  //this->operator+=(1);
  *this += 1;
  return *this;
}
// 后置++
Date Date::operator++(int)
{
  Date tmp(*this);
  //this->operator+=(1);
  tmp += 1;
  return tmp;
}
// 前置--
Date& Date::operator--()
{
  //this->operator-=(1);
  *this -= 1;
  return *this;
}
// 后置--
Date Date::operator--(int)
{
  Date tmp(*this);
  //tmp.operator-=(1);
  tmp -= 1;
  return tmp;
}
//友元函数
// Date d 最好加const
//ostream& operator<<(ostream& out, const Date d)
//{
//  out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
//  return out;
//}
// d << cout
//不能加const,加了报错,不能限制流的改变
ostream& Date::operator<<(ostream& out)
{
  out << _year << "年" << _month << "月" << _day << "日" << endl;
  return out;
}

🍖总结

本文实现了一个具体的日期类及其功能。

相关文章
|
2月前
|
存储 C++
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
25 2
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
|
2月前
|
C++
【C++】实现日期类相关接口(三)
【C++】实现日期类相关接口
|
2月前
|
C++
【C++】实现日期类相关接口(一)
【C++】实现日期类相关接口
|
2月前
|
C++
【C++】实现日期类相关接口(二)
【C++】实现日期类相关接口
|
4月前
|
安全 Java
12 Java常用类(二)(String类+时间类+BigDecimal类等等)
12 Java常用类(二)(String类+时间类+BigDecimal类等等)
40 2
|
7月前
|
Java
Java String类型转换成Date日期类型
Java String类型转换成Date日期类型
|
C++
【C++基础】实现日期类
【C++基础】实现日期类
66 0
|
存储 算法 C++
库中是如何实现string类的?
库中是如何实现string类的?
40 0
|
Java 数据库
Java 中日期String类型与Date类型相互转化
插入数据库时,存入当前日期,需要格式转换
105 0
|
安全 编译器 C++
[C++] 类与对象(中)完整讲述运算符重载示例 -- 日期类(Date) -- const成员2
[C++] 类与对象(中)完整讲述运算符重载示例 -- 日期类(Date) -- const成员2