【C++】如何用C++写一个日期计算器

简介: 【C++】如何用C++写一个日期计算器

前言

  写一个日期计算器是一个很有现实意义的。比如,我想弄一个倒计时或者你女朋友问你和她认识多少天了,这就需要让两个日期相减。又或者和别人约定几年以后怎么怎么样,这就得知道那一天的日期。

  写一个日期计算器对学习的意义也很大。初学C++,接触了类和对象的概念,又认识了默认成员函数,然后又学习了运算符的重载。而日期计算器就很好的涵盖了这些知识。能很好的帮助我们复习学过的知识。


  小编还会分享一下写代码时遇到的bug与错误。本篇内容如有不足之处,还请指正,小编会虚心接受并及时改进质量。



代码的布局

建两个 .cpp文件:Date.cpp      Test.cpp

建一个   .h文件  :Date.h

作用:Date.h声明一个类 ,      Date.cpp类的方法的具体实现,      Test.cpp测试方法的逻辑



设计数据

年:_year     月:_month     日:_day

变量名前面加“  _ ”符号,是为了和普通的数据或一些参数做区分。增加代码可读性。



方法声明

  // 获取某年某月的天数
  int GetMonthDay(int year, int month);
 
  // 全缺省的构造函数
  Date(int year = 2024, int month = 4, int day = 18);
 
  // 拷贝构造函数
  Date(const Date& d);
 
  // 赋值运算符重载
  Date& operator=(const Date& d);
 
  // 析构函数
  ~Date();
 
  // 日期+=天数
  Date& operator+=(int day);  //其结果为日期
 
  // 日期+天数
  Date operator+(int day);
 
  // 日期-天数
  Date operator-(int day);
 
  // 日期-=天数
  Date& operator-=(int day);  
 
  // 前置++
  Date& operator++();  //天数加1
 
  // 后置++
  Date operator++(int);
 
  // 后置--
  Date operator--(int);  //天数减1
 
  // 前置--
  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);  

声明的方法要有其意义,比如日期和天数相乘就没有意义,也没必要声明。



方法的实现

获取某年某月的天数

int GetMonthDay(int year, int month);

闰年

一年有365天,但地球公转的周期比一年多了大约5.82个小时。所以每过4年,二月的28天就要变成29天,即4年一润。但每四年都要润一次的话,每过100年,我们计算的天数要比地球公转的天数多了大概0.75天,所以二月的28天保持不变,即百年不润。100年不润是为了补足4年一润的精度,而400一润是为了补足100年不润的精度。只有这样,日期才不会与四季脱离。

总结就是:四年一润,百年不润,四百年又一润。

翻译成计算机语言就是

year % 4 == 0 && year % 100 != 0 || year % 400 == 0

有了闰年的概念,那么获取某年某月的天数的代码就可以实现了

// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)    
{
  assert(month > 0 && month < 13);
  
    static int a[13] = { -1,31, 28, 31, 30, 31, 30, 31, 31, 30, 31,30,31 };
    if (2 == month && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0))
    {
      return a[month] + 1;
    }
    else
    {
      return a[month];
    }
}

下图是代码控制的细节

下面加*的函数不做重点

*全缺省的构造函数

Date(int year = 2024, int month = 4, int day = 18);
Date::Date(int year, int month, int day)
{
  _year = year;
  _month = month;
  _day = day;
}

注意:全缺省构造函数在定义的时候不需要给缺省值。

* 拷贝构造函数

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

*赋值运算符重载

Date& operator=(const Date& d);
Date& Date::operator=(const Date& d) 
{
  if (this != &d)  
  {
    _year = d._year; 
    _month = d._month; 
    _day = d._day;
 
    return *this;
  }
 
  return *this;
}

*析构函数

因为没有涉及到资源管理可以不写,编译器会自动生成默认的析构函数。



日期+=天数

Date& operator+=(int day);

思路:可以把要加的天数直接加到日期的天数上,如果日期的天数没有超过该月的最大天数,直接返回日期。如果超过了,就写个循环往前进位,直到日期的天数小于该月的最大天数,然后再返回日期。

逻辑示意图

代码

Date& Date::operator+=(int day) 
{
  _day += day; //把天数加到日期的天数上
 
  while (_day > GetMonthDay(_year, _month)) //如果日期的天数大于该月最大天数就进位                                     
                                                           
  {
    _day -= GetMonthDay(_year, _month); //要想进位,得把该月的最大天数减掉
 
    ++_month; //进位
 
    if (_month > 12) //如果月不合法就调整月
    {
      ++_year;
      _month = 1;
        }
  }
 
  return *this;  //返回日期,因为出了作用域不会销毁,可以引用返回
}

*this是返回声明在头文件中的日期类,该类出了该函数的作用域不会销毁,所以传引用返回,提高效率。



日期+天数

Date operator+(int day);

实现日期+天数的时候不用把类似于日期+=天数的逻辑再写一遍,可以直接复用。

Date Date::operator+(int day)
{
  Date tmp = *this;  //在实例化对象的时候,调用拷贝构造函数,将日期类的数据拷贝给临时对象
  tmp += day;  //直接复用+=的逻辑
 
  return tmp;
}

日期加天数不能改变日期的值,所以要创建临时对象。临时对象出了作用域就销毁了,所以不能传引用返回。



日期-天数

Date operator-(int day);

在实现日期+=天数和日期+天数的时候,先实现了+=的逻辑,在实现+的逻辑的时候复用+=的逻辑。现在反过来,先实现-的逻辑,在实现-=的逻辑的时候复用-的逻辑。

思路:把天数直接和日期中的天数相减。若不为负数,直接返回。若为负数,则需要写个循环不断向前借位,如果把月借成负数就向年借,然后调整月,再调整日,直到日大于零为止。因为日期-天数不改变日期,所以要创建临时的对象。

代码

Date Date::operator-(int day)
{
  Date d = (*this); //创建临时对象,把日期类的数据拷贝给临时对象
 
  d._day -= day;  //让日期的天数直接和天数相减
 
  while (d._day <= 0)  //日期的天数小于零就调整
  {
    --d._month;  //该月已经是负的,应该往下个月借天数
 
    if (d._month <= 0)  //月不合法就调整月
    {
      --d._year;
      d._month = 12;
    }
 
    d._day += GetMonthDay(d._year, d._month); //把该月的所有天数都借给日期的天数
  }
 
  return d; 
 
}

逻辑示意图

因为临时对象出了作用域要销毁,所以不能传引用返回。

日期-=天数

Date& operator-=(int day);

直接复用-的逻辑,代码如下

Date& Date::operator-=(int day)
{
  return *this = *this - day;
}


前置++

Date& Date::operator++()

实现前置++就不需要复杂的逻辑了,只需要控制年月日的进位即可。代码如下

Date& Date::operator++()  //前置++需要先++在使用,所以不需要创建临时对象,返回值可以是引用
{
  ++_day;   //天数加一
  
  if (_day > GetMonthDay(_year, _month))  //如果天数不符合该月最大天数,则需要调整
  {
    ++_month;  //让月加一
    if (_month > 12)  //月不合法就调整月
    {
      ++_year;
      _month = 1;
      
    }
    _day = 1;  让天数置一
 
  }
 
  return *this;  //返回该类
}

后置++

Date Date::operator++(int)

可直接复用前置++,后置++需要先使用再++,所以需要创建临时对象,代码如下

Date Date::operator++(int)
{
  Date d = *this;
  
  ++(*this);
 
  return d;
}

后置--

Date Date::operator--(int)

与++的实现不同,--的话先实现后置再实现前置。代码如下

Date Date::operator--(int)
{
  Date d = *this;  // 创建临时对象,保存日期类中的值
 
  --_day;  //日期类中的天数减一
  if (_day <= 0) //这里可以不用写小于,因为一天一天的减是不可能跨过零来到负数的
  {
    --_month;  //如果天数等于零了,就需要借上个月的天数,月要减一
    if (_month <= 0) //月不合法就调整月
    {
      --_year;
      _month = 12;
    }
 
    _day = GetMonthDay(_year, _month);  //把天数置成该月最大天数
 
  }
 
  return d;  //返回保存好的数据,这样就实现了后置--的效果
}

前置--

Date& Date::operator--()

直接复用后置--,代码如下

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


实现比较大小运算符重载思路

小编先理一下思路,方便大家理解。

要实现比较大小的运算符有 >,  ==,  >=,   < , <= , !=。只需要实现> 和  ==就可以复用并实现后四个运算符。如下图

>运算符重载

bool Date::operator>(const Date& d)

代码如下

bool Date::operator>(const Date& d)
{
  if (_year > d._year)  //年大就大
  {
    return true;
  }
  if (_year == d._year)//年相等比月
  {
    if (_month > d._month) //月大就大
    {
      return true;
    }
    if (_month == d._month) //月相等比天
    {
      if (_day > d._day) //天大就大
      {
        return true;
      }
    }
  }
 
  return false; //不然就是小的
}

==运算符重载

bool Date::operator==(const Date& d)

年月日都相等才相等,代码如下

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


日期-日期

int Date::operator-(const Date& d)

参数:第一个参数为隐含的this指针,第二个参数为 const  Date&,传引用是为了提高传值效率。

返回值:日期-日期代表的是两个日期之间相差的天数,返回值类型为 int。

思路1:可以先算出两个日期相差多少年,把每一年的总天数加在一起,但要判断该年是否为闰年。

思路2:直接复用++运算符,在设一个变量,每加一天,变量就加一。

下面用思路2实现,代码如下

int Date::operator-(const Date& d)
{
  int counst = 0;  //定义一个变量,保存天数
 
  if ((*this) == d)  //如果两个日期相等,直接返回零
  {
    return 0;
  }
  else if ((*this) > d) 
  {
    Date tmp = d; //如果this的的日期大,就给d创建临时变量tmp,然tmp小日期去追this大日期
 
    while ((*this) != tmp)
    {
      ++tmp;
      counst++;
    }
 
    return counst; 
  }
  else
  {
    Date tmp = (*this); //同上
    
      while (tmp != d)    
      {
        ++tmp;
        counst++;
      }
 
      return counst;  
  }
 
  
}


代码错误和bug分享

小编在实现方法的时候把域作用限定符写在了返回值的前面,如下

大家不要这样写呀。

在写前置++的时候写了一个不易察觉的bug,写完测了几组数据没问题,但其他方法调用的时候却出问题了,调了好久才发现,如下代码,大家能看出来哪里出错了吗

// 前置++
Date& Date::operator++()  // bug分享   
{
  ++_day;   
  
  if (_day > GetMonthDay(_year, _month))
  {
    ++_month;
    if (_month > 12)
    {
      ++_year;
      _month = 1;
      _day = 1;
    }
    
 
  }
 
  return *this;
}

哈哈,其实正是因为逻辑太顺了,忽略了一些情况如下图

大家在测方法的时候尽量要跨过几个平年和闰年,这样方法才有可信度。


好啦,本篇的内容到此结束啦

相关文章
|
1月前
|
存储 C++
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
24 2
【C++篇】C++类和对象实践篇——从零带你实现日期类的超详细指南
|
1月前
|
C++
【C++】实现日期类相关接口(三)
【C++】实现日期类相关接口
|
1月前
|
C++
C++番外篇——日期类的实现
C++番外篇——日期类的实现
101 1
|
1月前
|
C++
【C++】实现日期类相关接口(二)
【C++】实现日期类相关接口
|
1月前
|
C++
【C++】实现日期类相关接口(一)
【C++】实现日期类相关接口
|
5月前
|
C++
【C++】日期类Date(详解)②
- `-=`通过复用`+=`实现,`Date operator-(int day)`则通过创建副本并调用`-=`。 - 前置`++`和后置`++`同样使用重载,类似地,前置`--`和后置`--`也复用了`+=`和`-=1`。 - 比较运算符重载如`&gt;`, `==`, `&lt;`, `&lt;=`, `!=`,通常只需实现两个,其他可通过复合逻辑得出。 - `Date`减`Date`返回天数,通过迭代较小日期直到与较大日期相等,记录步数和符号。 ``` 这是236个字符的摘要,符合240字符以内的要求,涵盖了日期类中运算符重载的主要实现。
|
5月前
|
C语言 C++
【C++】日期类Date(详解)③
该文介绍了C++中直接相减法计算两个日期之间差值的方法,包括确定max和min、按年计算天数、日期矫正及计算差值。同时,文章讲解了const成员函数,用于不修改类成员的函数,并给出了`GetMonthDay`和`CheckDate`的const版本。此外,讨论了流插入和流提取的重载,需在类外部定义以符合内置类型输入输出习惯,并介绍了友元机制,允许非成员函数访问类的私有成员。全文旨在深化对运算符重载、const成员和流操作的理解。
|
5月前
|
定位技术 C语言 C++
C++】日期类Date(详解)①
这篇教程讲解了如何使用C++实现一个日期类`Date`,涵盖操作符重载、拷贝构造、赋值运算符及友元函数。类包含年、月、日私有成员,提供合法性检查、获取某月天数、日期加减运算、比较运算符等功能。示例代码包括`GetMonthDay`、`CheckDate`、构造函数、拷贝构造函数、赋值运算符和相关运算符重载的实现。
|
5月前
|
C++
【C++】:日期类的实现 -- 日期计算器
【C++】:日期类的实现 -- 日期计算器
62 0
|
6天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
29 4