👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:课设
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨
一、准备工作
为了方便管理,我们可以创建多个文件来实现
Test.cpp
— 测试代码逻辑 (源文件)Date.cpp
— 逻辑实现 (源文件)Date.h
— 存放函数的声明 (头文件)
二、实现内容(Date.h)
#include <iostream>
using namespace std;
class Date
{
public:
// 友元函数
friend istream& operator>>(istream& in, Date& x);
friend ostream& operator<<(ostream& out, const Date& x);
// 写构造函数
// 全缺省(注意:声明给缺省值)
Date(int year = 1, int month = 2, int day = 3);
// 打印成员函数
void Print() const
{
cout << this->Year << '-' << this->Month << '-' << this->Day << endl;
}
// 以下是运算符重载
// ==
bool operator==(const Date& x) const;
// <
bool operator<(const Date& x) const;
// >=
bool operator>=(const Date& x) const;
// <=
bool operator<=(const Date& x) const;
// >
bool operator>(const Date& x) const;
// !=
bool operator!=(const Date& x) const;
// 获取某月的天数
static int GetMonthDay(int Year, int Month);
// 日期+=天数
Date& operator+=(int day);
// 日期-=天数
Date& operator-=(int day);
// 日期+day(返回的变量销毁了,不能用引用返回)
Date operator+(int day) const;
// 日期-day
Date operator-(int day) const;
// 前置++()
Date& operator++();
// 后置++
Date operator++(int);
// 前置--
Date& operator--();
// 后置--
Date operator--(int);
// 日期-日期 = 天数(int)
int operator-(const Date& x) const;
private:
int Year; // 年
int Month; // 月
int Day; // 日
};
三、代码实现(Date.cpp)
3.1 三个没必要写的成员函数
- 析构函数
成员类型都是内置类型,出了作用域,其变量自动销毁。因此可以不写
- 拷贝构造函数
编辑器的默认拷贝构造函数对内置类型是直接拷贝的
- 赋值运算符重载函数
编辑器的默认赋值操作符重载函数对内置类型也是直接拷贝的
3.2 构造函数
构造函数和以上三个默认成员函数不同,因为如果不自己定义构造函数,内置类型的默认构造函数默认是随机值
Date::Date(int year, int month, int day)
{
// 防止构造出非法日期
if (month >= 1 && month <= 12 && day > 0 && day <= Date::GetMonthDay(year, month))
{
Year = year;
Month = month;
Day = day;
}
else
{
cout << "非法日期" << endl;
assert(false);
}
}
注意:
- 当全缺省参数声明和定义分离的情况下,声明给缺省值。
- 类也可以让声明和定义分离。类声明放在
.h
文件中,成员函数定义放在.cpp
文件中,同时需要使用作用域限定符::
3.3 打印成员函数
像短小且频繁调用的函数可以直接放进类里,编译器会将其看作内联函数。
void Print() const
{
cout << this->Year << '-' << this->Month << '-' << this->Day << endl;
}
3.4 运算符重载之判断日期是否相等 (==)
// ==
bool Date::operator==(const Date& x) const
{
return (Year == x.Year)
&& (Month == x.Month)
&& (Day == x.Day);
}
3.5 运算符重载之比较日期大小(<)
// <
bool Date::operator<(const Date& x) const
{
// 年份大的则大
if (Year < x.Year)
return true;
// 月份大的则大(前提:年份相同)
else if (Year == x.Year && Month < x.Month)
return true;
// 日份大的则大(前提:年月份相同)
else if (Year == x.Year && Month == x.Month && Day < x.Day)
return true;
// 若不满足以上要求,则为假
return false;
}
当以上
<
和==
写完后,以下代码都可以复用了(3.6 ~ 3.9)
3.6 运算符重载之比较日期大小 (>=)
bool Date::operator>=(const Date& x) const
{
return !(*this < x);
}
3.7 运算符重载之比较日期大小 (<=)
bool Date::operator<=(const Date& x) const
{
return (*this < x || *this == x);
}
3.8 运算符重载之比较日期大小 (>)
bool Date::operator>(const Date& x) const
{
return !(*this <= x);
}
3.9 运算符重载之判断日期是否相当 (!=)
bool Date::operator!=(const Date& x) const
{
return !(*this == x);
}
3.10 日期 + 天数
首先对象加上一个数,本对象是不会发生改变的,例如
int i = 0;
i + 1;
// i 本身还是0
因此,可以拷贝构造一个临时对象,让临时对象加天数,因此可以服用+=
运算符,最后再返回即可
// 日期 + day
Date Date::operator+(int day) const
{
// 下面是拷贝构造函数,不是赋值运算符重载
Date tmp = *this;
tmp += day
// 可以直接复用(最好)
return tmp;
}
千万不能使用引用返回,因为当对象
tmp
生命周期结束,对象就不存在,可能会产生随机值。
3.11 日期 += 天数
// 获取某月的天数
int Date::GetMonthDay(int Year, int Month)
{
// 因为GetMonthDay函数会掉用很多次,每次调用都会为数组开辟空间
// 因此使用static修饰的变量就不会再每次调用函数的时候开辟变量空间,减少损耗
static int GetDay[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30,31,30,31 };
// 可能存在在闰年的情况
if (Month == 2 && Year % 400 == 0 || Year % 4 == 0 && Year % 100 != 0)
{
return 29;
}
return GetDay[Month];
}
// 日期+=天数
//d1 += 2
Date& Date::operator+=(int day)
{
// 可能出现输入的天数是负数
// 那么倒退即可
if (day < 0)
{
return *this -= -day;
}
this->Day += day;
// 可能会超过月份天数的最大值
while (this->Day > GetMonthDay(this->Year, this->Month))
{
// 超过就减当月的最大值
this->Day -= GetMonthDay(this->Year, this->Month);
// 然后月份进位
this->Month++;
// 如果月份进位超过12
if (this->Month > 12)
{
// 月份轮回
this->Month -= 12;
// 年份进位
this->Year++;
}
}
return *this;
}
日期+=天数
返回的结果是给到对象本身。当生命周期结束后,*this(对象)
不会被销毁,因此可以使用引用返回,提高效率。
3.12 日期 - 天数
Date Date::operator-(int day) const
{
Date tmp = *this;
tmp -= day
return tmp;
}
3.13 日期 -= 天数
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
this->Day -= day;
// 减去的日期可能存在负数日期
while (this->Day <= 0)
{
// 月份减一(借位)
this->Month--;
// 可能存在月份借完的情况
if (this->Month <= 0)
{
this->Month += 12;
// 年份借位
this->Year--;
}
this->Day += GetMonthDay(this->Year, this->Month);
}
return *this;
}
3.14 前置++
Date& Date::operator++()
{
*this += 1;
return *this;
}
3.15 后置++
// 后置++
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
前置++·
和后置++
会发生一个问题:函数名会相同。因此,C++
规定:后置(++/--)
重载时多增加一个int
类型的参数,但调用函数时该参数不用显示传递。
3.16 前置- -
Date& Date::operator--()
{
*this -= 1;
return *this;
}
3.17 后置- -
Date Date::operator--(int)
{
Date tmp = *this;
*this -= 1;
return tmp;
}
3.18 日期 - 日期
// d1 - d2
// 思路:循环计数的方法
int Date::operator-(const Date& x) const
{
// 假设一开始日期d2, 也就是*this比日期大
Date Max = *this;
Date Min = x;
int flag = 1;
// 特判存在日期2比日期1大
if (Min > Max)
{
Max = x;
Min = *this;
flag = -1;
}
int cnt = 0;
while (Min != Max)
{
Min++;
cnt++;
}
return cnt * flag;
}
四、流提取>>
那能否输入日期呢?其实是可以的。那么能否用cin
或者printf
函数呢?答案当然不可以。因为printf
只能根据格式化输出来打印的,例如%d
、%f
等内置类型,cout
它是能自动识别类型的,同样的只能识别内置类型。(如下图)
为了能让自定义类型也能使用到cin
,因此,可以通过重载>>
运算符来实现输入的操作:
>>
是一个双操作数:一个cin
对象(cin
是类对象,istream
的类对象),另一个是是日期类对象。注意:cin
是类对象。
istream& operator>>(istream& in, Date& x)
{
int year, month, day;
in >> year >> month >> day;
// 可能存在输入非法日期
if (month > 0 && month < 13 && day > 0 && day <= Date::GetMonthDay(year, month))
{
x.Year = year;
x.Month = month;
x.Day = day;
}
else
{
cout << "非法日期" << endl;
}
return in;
}
>>
运算符重载不能成为成员函数
成员函数的第一个参数默认是this
指针,它是指向当前对象的指针,而>>
的第一个参数是cin
,因此不能变成成员函数,那就只能定义在全局了。但是这里有个问题,那就是全局的函数就不能访问类中私有的成员变量了,这里就用 友元函数 来解决此问题,即在函数声明前加上friend
放在类中(详情见Date.h
)- 返回值是
istream&
而不是void
的原因:
如果返回值是void
,那它就不能连续输入。(cin >> d1 >> d2
)因为流插入的顺序是从左向右的,cin >> d1
后,返回的是void
类型,再cin>>d2
就会产生类型冲突,因此我们可以返回d1
的类型来解决此问题,而cin
的类型就是istream
- 两个形参能否用
const
修饰:
in>>d1
,in
是每次都键盘上输入不同的内容,因此不能用const
修饰;而d1
在没获取数据之前可能是随机值,然后再通过键盘获取数据,也就改变了数据内容,因此不能用const
修饰。
五、流插入<<
<<
有是一个双操作数:一个是cout
对象(cout
是类对象,是ostream
的类对象),另一个是日期类对象。
ostream& operator<<(ostream& out, const Date& x)
{
out << x.Year << "年" << x.Month << "月" << x.Day << "日" << endl;
return out;
}
out
不能用const
修饰的原因:
out
相当于终端控制台上,而每次数据输出在控制台都不一样,因此out
不能用const
修饰- 更多详细细节参考流提取
>>
五、源码地址
本篇博客代码地址:点击跳转