【C++要笑着学】从零开始实现日期类 | 体会代码的复用 | 提供完整代码(二)

简介: 啊,朋友们好啊。我是柠檬叶子C,上一章我们讲解了运算符重载,本篇将手把手从零开始一步步实现一个Date类,将会对每个步骤进行详细的思考和解读。

0x0A  体会复用的威力

📚 引入:


比如我们要实现 operator<,因为大于我们已经实现过了,


我们现在来写 <,可以直接把大于改成小于:


 
         


…… 既然都能这样了,那为什么不用用神奇的复用呢?


我们已经把 operator> 和 == 实现了,剩下的这些我们都可以复用解决。


技巧:对于类的比较,实现一个 > 和 == 其它直接复用就完事了。


(当然,实现一个 < 和 == 也行,随你便)


这个技巧不仅仅针对于日期类的比较,所有类要实现比较,都可以用这种方式,


除非它们不互斥(  >= 取反是 < ,这就是互斥 )。


bool operator>(const Date& d) const;            // d1 > d2
bool operator==(const Date& d) const;           // d1 == d2

0x0B  判断大于等于 operator>=

💬 Date.h


bool operator>=(const Date& d) const;            // d1 >= d2

💬 Date.cpp


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

直接复用解决就完事了,这波真是秦始皇被雷劈,嬴麻了。


0x0C  判断小于 operator<

💬 Date.h


bool operator<(const Date& d) const;            // d1 < d2

小于,不就是既不大于也不等于嘛。


所以我们直接复用 operator>= 就完事了。


💬 Date.cpp


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

这里取反就可以了,如果 d1 >= d2 为真,取反后 ! 则返回假。反之返回真。


0x0D  判断小于等于 operator<=

💬 Date.h


bool operator<=(const Date& d) const;     // d1 <= d2

💬 Date.cpp


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

小于等于,就是 > 取反,没什么好说的。


这里你还可以用  <  || == ,也可以的。


0x0E  判断日期是否不相等 operator!=

💬 Date.h


bool operator!=(const Date& d) const;     // d1 != d2

不等于和等于逻辑是相反的,直接反 ! 就完事了。


💬 Date.cpp


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

0x0F  日期减日期 operator-

我们刚才实现的 operator- 是日期减天数的:


/* d1 - 100 */
Date Date::operator-(int day) const {
  Date ret(*this);   // 拷贝构造一个d1
  ret -= day;        // ret.operator-=(day);
  return ret;
}

❓ 如果我们想要计算一下距离暑假还有多少天呢?

e8d239481152db4c7d36ab2e528db4e9_97cb9a8e76d84ddaaaff90745eae2335.png

那我们就需要写一个日期减日期版本的 operater-



💬 Date.h

Date operator-(int day) const;         // holiday - today

我们现在想的是日期减日期,我们可以让小的天数去加大的天数。


💬 Date.cpp


/* holiday - today */
Date Date::operator-(const Date&d) const {
  // 我默认认为第一个大,第二个小
  Date max = *this;
  Date min = d;
  int flag = 1;      // 立个flag
  // 如果第一个小,第二个大
  if (*this < d) {
  max = d;
  min = *this;
  flag = -1;     // 说明我们立的flag立错了,改成-1
  }
  int count = 0;
  while (min != max) {
  min++;
  count++;
  }
  return count * flag;
}


我们可以用立 flag 法。


我们先立个flag 假设 —— 第一个日期大,第二个日期小。


flag 为 1 表示第一个大第二个小,flag 为 -1 表示第一个小第二个大。


分别创建 max 和 min 表示第一个日期(d1)和第二个日期(d2)。


然后走一遍判断,核实一下立的 flag 对不对,不对的话就纠正一下。


之后我们用计数器的方式来实现就可以了。


💬 test.cpp


void DateTest8() {
  Date today(2022, 1, 17);
  Date holiday(2022, 7, 1);
  cout << (holiday - today) << endl;
  cout << (today - holiday) << endl;   
}


0x10  打印今天是周几 PrintWeekDay

❓ 打印今天是周几什么什么意思?


" 打印今天是周几,就是打印今天是星期几。"   —— 节选自《 节选自 <节选> 》


💬 Date.h


void PrintWeekDay() const;

💬 Date.cpp


void Date::PrintWeekDay() const {
  // 1900.1.1 日开始算
  const char* week[] = { "周一", "周二", "周三", "周四", "周五", "周六", "周日" };
  Date start(1900, 1, 1);
  int count = *this - start;
  cout << week[count % 7] << endl;
}

我们拿 1900年1月1号 作为起始点,两个日期相减得到的结果,


%7 作为下标去访问 week 数组里我们已经准备好的周几,打印出来就可以了。


⚡ 这个 start 反正我们就在这用一下而已,后面也用不着了,这里就可以使用匿名对象:


void Date::PrintWeekDay() const {
  // 1900.1.1 日开始算
  const char* week[] = { "周一", "周二", "周三", "周四", "周五", "周六", "周日" };
  int count = *this - Date(1900, 1, 1);  // 可以使用匿名对象
  cout << week[count % 7] << endl;
}

💬 test.cpp


void DateTest9() {
  Date d1(2021, 3, 19);
  d1.PrintWeekDay();
}
int main(void)
{
  DateTest9();
  return 0;
}

60ae08f524d2c8181b2d8d30503eeff8_f2ae02d667e14957a6d3938dc803f162.png


大功告成!!!


Ⅱ.  完整代码


0x00  Date.h

#include <iostream>
using namespace std;
class Date {
public:
  Date(int year = 1, int month = 1, int day = 1);    // 全缺省构造
  int GetMonthDay(int year, int month) const ;       // 获取某年某月对应的天数
  void Print() const;                                // 打印函数
  bool operator>(const Date& d) const;               // d1 > d2
  bool operator==(const Date& d) const;              // d1 == d2 
  bool operator>=(const Date& d) const;          // d1 >= d2
  bool operator<(const Date& d) const;           // d1 < d2
  bool operator<=(const Date& d) const;          // d1 <= d2
  bool operator!=(const Date& d) const;          // d1 != d2
  Date& operator+=(int day);               // d1 += 100
  Date& operator-=(int day);               // d1 -= 100
  Date operator+(int day) const;                 // d1 + 100
  Date operator-(int day) const;          // d1 - 100
  Date& operator++();                       // ++d1;
  Date operator++(int);                   // d1++;
    Date& operator--();                                // --d1;
    Date operator--(int);                              // d1--;
  int operator-(const Date& d) const;                // 日期减日期
  void PrintWeekDay() const;                         // 返回星期几
private:
  int _year;
  int _month;
  int _day;
};

0x01  Date.cpp

#include "Date.h"
/* 获取月对应的天数 */
int Date::GetMonthDay(int year, int month) const {
  static int monthDatArray[13] = { 0, 
  31, 28, 31, 30, 31, 30, 
  31, 31, 30, 31, 30, 31 
  };                                          // 简陋哈希
  int day = monthDatArray[month];             // 获取天数
  if (month == 2                              // 先判断是否为二月
  && ((year % 4 == 0 && year % 100 != 0)  // 是二月,再判断是否是闰年
  || (year % 400 == 0))) {
  day += 1;                               // 是闰年,天数+1
  }
  return day;
}
/* 全缺省构造函数 */
Date::Date(int year, int month, int day) {
  this->_year = year;
  this->_month = month;
  this->_day = day;
  // 判断日期是否合法
  if (!(_year >= 0
  && (month > 0 && month < 13) 
  && (day > 0 && _day <= GetMonthDay(year, month)))) {
  cout << "非法日期: ";
  this->Print();  // 访问成员函数(this可省略)
  }
}
/* 打印函数 */
void Date::Print() const {
  printf("%d-%d-%d\n", this->_year, this->_month, this->_day);
}
/* d1 > d2 */
bool Date::operator>(const Date& d) const {
  if (this->_year > d._year) {
  return true;
  } 
  else if (this->_year == d._year 
  && this->_month > d._month) {
  return true;
  } 
  else if (this->_year == d._year
  && this->_month == d._month
  && this->_day > d._day) {
  return true;
  } 
  else {
  return false;
  }
}
/* d1 == d2 */
bool Date::operator==(const Date& d) const {
  return this->_year == d._year
  && this->_month == d._month
  && this->_day == d._day;
}
/* d1 >= d2 */
bool Date::operator>=(const Date& d) const {
  return *this > d || *this == d;
}
/* d1 < d2 */
bool Date::operator<(const Date& d) const {
  return !(*this >= d);
}
/* d1 <= d2 */
bool Date::operator<=(const Date& d) const {
  return !(*this > d);
}
/* d1 != d2*/
bool Date::operator!=(const Date& d) const {
  return !(*this == d);
}
/* d1 += 100 */
Date& Date::operator+=(int day) {
  if (day < 0) {
  return *this -= -day;
  }
  this->_day += day;  // 把要加的天数倒进d1里
  while (this->_day > GetMonthDay(this->_year, this->_month)) {   // 判断天数是否需要进位
  this->_day -= GetMonthDay(this->_year, this->_month);   // 减去当前月的天数
  this->_month++;  // 月份+1
  if (this->_month == 13) {   // 判断月份是否需要进位
    this->_month = 1;  // 重置月份
    this->_year++;   // 年份+1
  }
  }
  return *this;
}
/* d1 -= 100 */
Date& Date::operator-=(int day) {
  if (day < 0) {
  return *this += -day;
  }
  this->_day -= day;
  while (this->_day <= 0) {   // 天数为0或小于0了,就得借位,直到>0为止。
  this->_month--;  // 向月借
  if (this->_month == 0) {   // 判断月是否有得借
    this->_year--;   // 月没得借了,向年借
    this->_month = 12;  // 年-1了,月份置为12月
  }
  this->_day += GetMonthDay(this->_year, this->_month);  // 把借来的天数加到_day上
  }
  return *this;
}
/* d1 + 100 */
Date Date::operator+(int day) const {
  Date ret(*this);  // 拷贝构造一个d1
  ret += day;       // ret.operator+=(day);
  return ret;   // 出了作用域ret对象就不在了,所以不能用引用返回
}
/* d1 - 100 */
Date Date::operator-(int day) const {
  Date ret(*this);   // 拷贝构造一个d1
  ret -= day;        // ret.operator-=(day);
  return ret;
}
/* ++d1 */
Date& Date::operator++() {
  *this += 1;
  return *this;
}
/* d1++ */
Date Date::operator++(int) {
  Date ret(*this);   // 拷贝构造一个d1
  *this += 1;
  return ret;
}
/* --d1 */
Date& Date::operator--() {
    *this -= 1;
    return *this;
}
/* d1-- */
Date Date::operator--(int) {
    Date ret(*this);   // 拷贝构造一个d1
    *this += 1;
    return ret;
}
/* 日期减日期 */
int Date::operator-(const Date& d) const {
  // 我默认认为第一个大,第二个小
  Date max = *this;
  Date min = d;
  int flag = 1;      // 立个flag
  // 如果第一个小,第二个大
  if (*this < d) {
  max = d;
  min = *this;
  flag = -1;     // 说明我们立的flag立错了,改成-1
  }
  int count = 0;
  while (min != max) {
  min++;
  count++;
  }
  return count * flag;
}
/* 返回星期几 */
void Date::PrintWeekDay() const {
  // 1900.1.1 日开始算
  const char* week[] = { "周一", "周二", "周三", "周四", "周五", "周六", "周日" };
  int count = *this - Date(1900, 1, 1);  // 可以使用匿名对象
  cout << week[count % 7] << endl;
}


0x02  test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "Date.h"
void DateTest1() {
  Date d4(2022, 2, 29);
  d4.Print();
  Date d5(2009, 2, 29);
  d5.Print();
  Date d6(1998, 2, 29);
  d6.Print();
}
void DateTest2() {
  Date d1(2022, 2, 1);
  Date d2(2012, 5, 1);
  cout << (d1 > d2) << endl;
  Date d3(2022, 3, 15);
  cout << (d1 > d3) << endl;
  Date d4(d1);
  cout << (d1 > d4) << endl;
}
void DateTest3() {
  Date d1(2022, 1, 16);
  d1 += 100;   // 让当前天数+100天
  d1.Print();
}
void DateTest4() {
  Date d1(2022, 1, 16);
  Date ret = d1 + 100;
  d1.Print();
  ret.Print();
}
void DateTest5() {
  Date d1(2022, 3, 20);
  d1 -= 100;  // 2021, 12, 10
  d1.Print();
}
void DateTest6() {
  Date d1(2022, 1, 17);
  Date ret = d1 - -100;
  ret.Print();
}
void DateTest7() {
  Date d1(2022, 3, 20);
  Date ret1 = d1++;   // d1.operator++(&d1, 0);
  Date ret2 = ++d1;   // d1.operator++(&d1);
}
void DateTest8() {
  Date today(2022, 1, 17);
  Date holiday(2022, 7, 1);
  int ret1 = holiday - today;
  int ret2 = today - holiday;
  cout << ret1 << endl;
  cout << ret2 << endl;   
}
void DateTest9() {
  Date d1(2022, 3, 19);
  d1.PrintWeekDay();
}
int main(void)
{
  // DateTest1();
  // DateTest2();
  // DateTest3();
  // DateTest4();
  // DateTest5();
  // DateTest6();
  // DateTest7();
  // DateTest8();
  // DateTest9();
  return 0;
}


相关文章
|
4天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
18 2
|
10天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
37 5
|
16天前
|
算法 安全 C++
提高C/C++代码的可读性
提高C/C++代码的可读性
35 4
|
16天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
47 4
|
18天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
44 4
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
28 4
|
2月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
25 4
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
22 1
|
2月前
|
存储 编译器 C语言
【C++打怪之路Lv3】-- 类和对象(上)
【C++打怪之路Lv3】-- 类和对象(上)
17 0
|
2月前
|
存储 编译器 C语言
深入计算机语言之C++:类与对象(上)
深入计算机语言之C++:类与对象(上)