【C++】类和对象(中)—— 日期类的实现 | const成员函数

简介: 【C++】类和对象(中)—— 日期类的实现 | const成员函数

一. show time 少年


继类和对象上篇中篇后,我们把这些学到的知识都用起来,来写一个日期类。下面我贴出头文件,能独立写完,算是真正弄懂了。所有要注意的小点我都写出来了,文章尽量呈现知其然知其所以然,let’s go


#include<iostream>
using namespace std;
class Date
{
public:
  //构造函数
  Date::Date(int year = 0, int month = 1, int day = 1);
  //析构函数、拷贝构造、赋值重载都不用写
  //Date(const Date& d)
  //{
  //  cout << "Date(const Date& d)" << endl;
  //}
  void Print();
  int getMonthDay(int year, int month);
  //运算符重载系列
  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);
  // d1 += 100;
  Date& operator+=(int day);
  // d1 + 100;
  Date operator+(int day);
  // d1 -= 100;
  Date& operator-=(int day);
  // d1 - 100;
  Date operator-(int day);
  // 前置++
  Date& operator++();
  // 后置++
  Date operator++(int);
  // 前置--
  Date& operator--();
  //后置 --
  Date operator--(int);
  // 日期 - 日期
  int operator-(const Date& d);
  // 今天是星期几?
  void PrintWeekday();
private:
  int _year;
  int _month;
  int _day;
};


二. 基本接口


🐋默认成员函数


🎨构造函数

通过之前的学习,我们知道日期类是不需要写析构和拷贝构造函数,用默认生成的即可,我们只需要完成构造函数


//检查正确的日期
bool checkDate()
{
  if (_year >= 1
  && _month>0 && _month<13
  && _day>0 && _day <= GetMonthDay(_year, _month))
  {
  return true;
  }
  else
  return false;
}
//构造会频繁的调用,所以直接放在类里面定义作为inline
Date(int year = 2022, int month = 1, int day = 1)
{
  _year = year;
  _month = month;
  _day = day;
  if (!checkDate())
  {
  Print();
  cout << "刚构造的日期非法" << endl;
  }
}


输入天数的同时还要判断是否为非法日期:2021年10月90日,为了判断天数是否合理,我们引入了下一个接口,来获取某年某月的天数 ——


🎨拷贝构造函数

日期类的可以不用写,系统自动生成的即可完成,不懂的回去看看这篇博客:复习跳转


🐋获取某年某月天数


我们用数组来储存每个月的天数,第几月就对于多少天


因为频繁的调用,数组设为静态的,节省空间

只有2月的天数随着是否为闰年改变而改变

对闰年的处理,先来判断month == 2(反过来就没这么好了,细节)

//获取某年某月的天数
int GetMonthDay(int year, int month)
{
  //静态:不用多次开空间
  static int days[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
  int day = days[month];
  //闰年:四年一闰、百年不闰、四百年再闰
  if (month == 2
  && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))//先判断是否2月
  {
  day += 1;
       }
  return day;
}


三.一系列运算符重载


⚡+=、+、++


🌍日期+=天数

2022年10月12日 +200天,但顺着我们正常的计算思维,列写几个例子,其实很简单。✅它其实就是一个不断进位的过程,天满了往月进;月满了往年进、月归1


0a2653c851af460fa595bd959398a8f1.png


//d2 += d1 += 100   连续加等,有返回值
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;//d1没销毁,可以传引用返回
}


注意我们这里改变了实现的是+=,而不是+,因为改变了day ——

int i = 100;

i + 100;


此处i的值并不会变,那该怎么样实现+的重载呢,很简单给它来一个临时对象


🌍日期+天数

拷贝构造一个临时变量,避免对d的改变

巧妙复用 +=

// d1 + 100   不能改变d1
Date Date::operator+(int day)
{
  Date tmp = (*this);//拷贝构造一个
  tmp += day;// tmp.operator(&tmp,)
  return tmp;
}


🌍前置++、后置++

前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载

C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递


🔥注意:判断是否是传引用返回or传值返回:是要根据实现逻辑的不同,看你返回的对象,出了作用域被没被销毁,分析出来加上的,当然熟练了就直接写了

🎨前置++


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


🎨后置++


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


他们在编译器眼里会转化成:


Date d1(2022,10,12)
++d1;//d1.operator(&d1)
d1++;//d2.operator(&d1, 1)


如果是对于自定义类型,建议使用前置++。这是因为前置++,我们可以传引用返回;后置++,拷贝构造临时对象加上传值返回,要拷贝构造两次(传参构造一次、返回值构造一次),效率低下


⚡+=、+、++


哎呀有了之前的知识铺垫,后面的不就信手拈来? 哈哈别太自信


🌍日期 -= 天数

怎么样减呢?根据加的思路差不多,不够找月借,再不够找年借


0a2653c851af460fa595bd959398a8f1.png


注:


天数 <= 0都不合法,要借位

注意我们借到的天数是上一个月的天数(这与“加”不同,“加”获取的是本月天数)

到达边界时,我们是先把月置12,再对天数处理

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


❌这时候问题就来了,万一我们输入的天数是负数怎么办?


我们则需要把它单独拿出来处理,-=day 等价于 +=(-day),加一个负数不就等于减一个整数


0a2653c851af460fa595bd959398a8f1.png


同样的刚刚我们实现的+=运算符重载函数也需要同样处理,代码如下 ——


//+=
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;//d1没销毁,可以传引用返回
}


//-=
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)
{
  Date ret(*this);
  ret -= day;
  return ret;
}


🌍前置–、后置–

注意点和前置++一样


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


⚡一堆比较== 、!=、>、>=、<、<=

任何一个类,只需要写一个 > == 重载 或者 < == 即可,剩下比较运算符复用即可


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


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


下面开始疯狂的复用吧


// d1 != d2
bool Date::operator!=(const Date& d)
{
  return !(*this == d);
}


//d1 >= d2
bool Date::operator>=(const Date& d)
{
  return (*this == d) || (*this > d);
}


// d1 < d2
bool Date::operator<(const Date& d)
{
  return !(*this >= d);
}


//d1 <= d2
bool Date::operator<=(const Date& d)
{
  return !(*this > d);
}


四.日期-日期


这里我们计算一个我们离实习的时间吧


//日期-日期
// offerDay - today =>offerDay.operator(&offerday,today);
int Date::operator-(const Date& d)
{
  int flag = 1;//防止天数为负
  Date max = *this;//晚
  Date min = d;//早
  if (*this < d)
  {
  max = d;
  min = *this;
  flag = -1;
  }
  int day = 0;
  while (min != max)
  {
  ++min;
  ++day;
  }
  return  day* flag;
}


五、计算星期几


void Date::PrintWeekday()
{
  Date start(1900, 1, 1); //查询得星期一
  int count = *this - start;
  cout << "星期" << ((count % 7) + 1) << endl;
}


六、const成员


举例:


0a2653c851af460fa595bd959398a8f1.png


同理:d2 < d1 也会出错


2d65d23f6d4748949b924e4057485923.png


于是C++增加了const成员函数,实际修饰this指针,保护this指向的内容不被修改,即不能对任何类成员进行修改


class Date
{
public:
  void Display() const
  {
  cout << "year:" << _year << endl;
  cout << "month:" << _month << endl;
  cout << "day:" << _day << endl << endl;
  }
private:
  int _year; 
  int _month; 
  int _day; 
};
int main()
{
  Date d1;
  d1.Display(); //权限缩小
  const Date d2;
  d2.Display(); //权限不变
  return 0;
}


const Date* const this


第一个const修饰:this指针指向的内容

第二个const修饰:this指针本身不能被修改

也就是说const对象和非const对象都可以调用这个成员函数


七、取地址及const取地址操作符重载


这两个函数意义不大


这两个默认成员函数一般不用重新定义 ,编译器默认会生成的就足够用了


class Date
{ 
public :
   Date* operator&()
   {
     return this ;
   }
   const Date* operator&()const
   {
     return this ;
   }
private :
   int _year ; // 年
   int _month ; // 月
   int _day ; // 日
};


这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容


八、实现代码


🎉Date.cpp

#define _CRT_SECURE_NO_WAENINGS
#include "Date.h"
void Date::Print()
{
  cout << _year << "-" << _month << "-" << _day << endl;
}
//任何一个类,只需要写一个 > == 重载 或者 < == 即可,剩下比较运算符复用即可
bool Date::operator==(const Date& d)
{
  return _year == d._year
  && _month == d._month
  && _day == d._day;
}
// d1 != d2
bool Date::operator!=(const Date& d)
{
  return !(*this == d);
}
//d1 > d2
bool Date::operator>(const Date& d)
{
  if((_year>d._year)
  ||(_year==d._year &&_month > d._month)
  ||(_year==d._year && _month==d._month && _day>d._day))
  {
  return true;
  }
  else
  {
  return false;
  }
}
//尽可能的去复用
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);
}
// d1 + 100
Date Date::operator+(int day)
{
  Date tmp = (*this);
  tmp += day;
  return tmp;
}
//d2 += d1 += 100   连续加等,有返回值
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;//d1没销毁,可以传引用返回
}
Date Date::operator-(int day)
{
  Date ret(*this);
  ret -= day;
  return ret;
}
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--() //前置
{
  *this -= 1;
  return *this;
}
Date Date::operator--(int) //后置
{
  Date ret = *this;
  *this -= 1;
  return ret;
}
Date& Date::operator++() //前置++
{
  *this += 1;
  return *this;
}
Date Date::operator++(int) //后置++
{
  Date ret = *this;
  *this += 1;
  return ret;
}
//日期-日期
// offerDay - today =>offerDay.operator(&offerday,today);
int Date::operator-(const Date& d)
{
  //假设
  int flag = 1;//防止天数为负
  Date max = *this;//晚
  Date min = d;//早
  if (*this < d)
  {
  max = d;
  min = *this;
  flag = -1;
  }
  int day = 0;
  while (min < max)
  {
  ++min;
  ++day;
  }
  return  day* flag;
}
void Date::PrintWeekday()
{
  Date start(1900, 1, 1); //查询得星期一
  int count = *this - start;
  cout << "星期" << ((count % 7) +1) << endl;
}


🎉Test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "Date.h"
void test1()
{
  Date d1(2002, 3, 7);
  d1.Print();
  Date d2(2022, 2, 29);
}
//测试+、+=、++
void test2()
{
  Date d1(2022, 1, 16);
  /*Date ret = d1 + 100;
  ret.Print();*/
  //Date d2(2022, 1, 16);
  //d2 += 100;
  //d2.Print();
  //++d1;
  d1++;
}
//测试这一堆运算符重载函数
void test3()
{
  Date d1(2002, 3, 7);
  Date d2(2002, 2, 19); //missing lmyy
  Date d3(2002, 3, 7);
  cout << (d1 == d3) << endl;
  cout << (d1 >= d2) << endl;
  cout << (d1 < d3) << endl;
  cout << (d1 <= d2) << endl;
  cout << (d1 != d3) << endl;
}
// 测试-,-=,--
void test4()
{
  Date d1(2022, 1, 10);
  Date d2(2022, 2, 19);
  Date ret2 = d2 - 60;
  ret2.Print();
  d1 -= 10;
  d1.Print();
  /*--d2;
  d2--;*/
}
//测试日期 - 日期,星期几
void test5()
{
  Date today(2022,1,23);
  Date offerDay(2022, 9, 1);
  cout << (offerDay - today) << endl;
  today.PrintWeekday();
}
int main()
{
  //test1();
  //test2();
  //test3();
  //test4();
  test5();
  return 0;
}



📢写在最后

英雄联盟世界赛要准备开打了,有爱看的兄弟吗?


相关文章
|
18天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
21天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
21 4
|
21天前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
19 4
|
21天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
17 1
|
1月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
1月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)
|
1月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
23 3
|
1月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
51 1
|
1月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
18 1
|
1月前
|
C++
C++番外篇——日期类的实现
C++番外篇——日期类的实现
76 1