🍖前言
通过类和对象的基本学习,我们可以实现一个完整的日期类。本文探讨日期类如何实现。
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; }
🍖总结
本文实现了一个具体的日期类及其功能。