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


相关文章
|
2月前
|
C++ Windows
应用程序无法正常启动(0xc0000005)?C++报错0xC0000005如何解决?使命召唤17频频出现闪退,错误代码0xC0000005(0x0)
简介: 本文介绍了Windows应用程序出现错误代码0xc0000005的解决方法,该错误多由C++运行库配置不一致或内存访问越界引起。提供包括统一运行库配置、调试排查及安装Visual C++运行库等解决方案,并附有修复工具下载链接。
1044 1
|
4月前
|
API 数据安全/隐私保护 C++
永久修改机器码工具, exe一机一码破解工具,软件机器码一键修改工具【c++代码】
程序实现了完整的机器码修改功能,包含进程查找、内存扫描、模式匹配和修改操作。代码使用
|
5月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
139 0
|
5月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
222 0
|
5月前
|
C++
爱心代码 C++
这段C++代码使用EasyX图形库生成动态爱心图案。程序通过数学公式绘制爱心形状,并以帧动画形式呈现渐变效果。运行时需安装EasyX库,教程链接:http://【EasyX图形库的安装和使用】https://www.bilibili.com/video/BV1Xv4y1p7z1。代码中定义了屏幕尺寸、颜色数组等参数,利用随机数与数学函数生成动态点位,模拟爱心扩散与收缩动画,最终实现流畅的视觉效果。
802 0
|
7月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
273 12
|
8月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
8月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
8月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
9月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。