【C++深入学习】日期类函数从无到有实现
零、本文思维导图
一、前期准备
1.1 检查构造的日期是否合法
//Date.cpp
bool Date::CheckDate()
{
if (_month < 1 || _month > 12
|| _day < 1 || _day > GetMonthDay(_year, _month))
{
return false;
}
else
{
return true;
}
}
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
//防止构造的日期有问题
if (!CheckDate())
{
cout << "非法日期:" << endl;
Print();
}
}
1.2 获取某年的某月的总天数
- 建议直接写在类里面作为成员函数:定义在类里面的成员函数默认是内联
inline
,而且该函数不仅短小,还会被频繁调用;
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);//避免出现非法月份??????
static int GetMonthDayArray[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
//先判断是否为2月
{
return 29;//闰年
}
return GetMonthDayArray[month];
}
1.3 打印函数
void Date::Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
二、日期+天数
2.1 operator+=
- 进位:
时间在向前
先直接相加,日期不合法,减天加月,月满加年,直至日期合法。 - 返回值:返回
*this
,所以使用引用返回。 - 注意:这里是
+=
,而不是+
。a+1
:a本身不变;a+=1
:a本身是会变的。
//日期+天数:d1+=100
Date& Date::operator+= (int day)
{
if(day < 0)
{
return *this-=(-day);//day为负数的情况
}
//正常+:2024/7/12+10=2024/7/22
_day += day;
while (_day > GetMonthDay(_year, _month))//判断日期是否非法
{
//时间在前进:
_day -= GetMonthDay(_year, _month);//先减天
++_month;//再加月,判断是否满月,满月进年
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
2.2 operator+
- 使用传值返回
- 直接写:
//d1 + 100
Date Date::operator+ (int day)
{
Date tmp = *this;//这里拷贝一份出来
tmp._day += day;
while (tmp._day > GetMonthDay(tmp._year, tmp._month))
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
++tmp._month;
if (tmp._month == 13)
{
tmp._year++;
tmp._month = 1;
}
}
return tmp;//这里就不能使用引用返回了,局部对象,出作用域就会销毁
}
- 上面的是直接写的,也可以在写了
operator+=
后,+
复用+=
:
//d1 + 100
Date Date::operator+ (int day)
{
Date tmp = *this;//这里拷贝一份出来
tmp += day;//复用+=
return tmp;//这里就不能使用引用返回了,局部对象,出作用域就会销毁
}
三、日期-天数
3.1 operator-=
//d1 -= 100
Date& Date::operator-=(int day)
{
if(day < 0)
{
return *this += (-day);//day是负数的情况
}
_day -= day;
while (_day <= 0)//判断出非法日期
{
--_month;//先借月
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);//加上借来的
}
return *this;
}
3.2 operator-
- 直接实现:
Date Date::operator-(int day)
{
Date tmp = *this;
tmp._day -= day;
while (tmp._day<=0)
{
--tmp._month;
if (tmp._month == 0)
{
tmp._month = 12;
--tmp._year;
}
tmp._day += GetMonthDay(tmp._year, tmp._month);
}
return tmp;
}
-
复用-=
//d1 - 100
Date Date::operator-(int day)
{
Date tmp = *this;
tmp -= day;
return tmp;
}
3.3 两种复用对比
-=
复用-
Date Date::operator-(int day)
{
Date tmp = *this;//拷贝1
tmp._day -= day;
while (tmp._day<=0)
{
--tmp._month;
if (tmp._month == 0)
{
tmp._month = 12;
--tmp._year;
}
tmp._day += GetMonthDay(tmp._year, tmp._month);
}
return tmp;//拷贝2
}
Date& Date::operator-=(int day)
{
/*Date tmp = *this - day;
*this = tmp;*/
*this = *this - day;//赋值也是一种拷贝
return *this;
}
// 拷贝的次数比第一种多
-
复用-=
(相对较好)
//d1 -= 100
Date Date::operator-(int day)
{
Date tmp = *this;//拷贝1
tmp -= day;
return tmp;//拷贝2
}
//-的拷贝次数都是一样的
Date& Date::operator-=(int day)//无拷贝
{
_day -= day;
while (_day <= 0)//判断出非法日期
{
--_month;//先借月
if (_month == 0)
{
_month = 12;
--_year;
}
_day += GetMonthDay(_year, _month);//加上借来的
}
return *this;//传引用返回
}
- 两种
operator-
的拷贝次数一样; - 第二种的
-=
是自己实现的,全程无拷贝;但是第一种-=
复用-
:前面-
的两次拷贝再加上自己本身的一次赋值拷贝。因此第二种相对较好。
四、日期比较
4.1 operator<
先判断所有<
并将其归为一类,均为true
。
//<运算符的重载
bool Date::operator<(const Date& d)
{
//true为一类
if (_year < d._year)
{
return true;
}
else if (_year == d._year)
{
if (_month < d._month)
{
return true;
}
else if (_month == d._month)
{
if (_day < d._day)
{
return true;
}
}
}
//false为一类
return false;
}
4.2 operator==
年月日均相等,则为true
。
//==运算符的重载
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
4.3 其他关系比较
在写了operator<
(或者operator>
)和operator=
两个之后,就可以根据去翻等个侯总逻辑关系表示出其他的关系符。
<=
运算符的重载
//<=运算符的重载
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);
}
五、++
- 前置++用的比较多,而且拷贝比较少。
- 重载++运算符时,有前置++和后置++,运算符重载函数名都是
**operator++**
,无法很好的区分。C++规定,后置++重载时,增加一个**int**
形参,跟前置++构成函数重载,方便区分。
5.1 前置++
//1.前置++:d1.operator++()
Date& Date::operator++()//没有拷贝
{
//Date tmp = *this;//可省略不写
//复用 operator+=
*this += 1;
return *this;
}
5.2 后置++
//2.后置++:d1.operator++(0)(括号里面只要求整数)
Date Date::operator++(int)//有拷贝
{
Date tmp = *this;//拷贝构造,用于返回
*this += 1;
return tmp;
}
六、-- (和++相似)
6.1 前置–
//1.前置--运算符重载
Date& Date::operator--()
{
//Date tmp = *this;
//复用 operator+=
*this -= 1;
return *this;
}
6.2 后置–
//2.后置--运算符重载
Date Date::operator--(int)
{
Date tmp = *this;
//复用 operator-=
*this -= 1;
return tmp;
}
七、日期-日期
计算两个日期之间相差的天数。
- 法一:直接相减,非常繁琐而且容易出错,借月后从12月份开始而不是从1月份开始;
- 法二:从年初开始,做年的减法和月日的减法,如下图条框内做减法。
- 法三:
思路:在生活中,我们有些人算数有一种习惯,例如计算8 + 4
,我们就会数:9,10,11,12
,数到四个数。这里也是用的这个思路。从小的日期一直数到大的日期,同时用计数器进行计数。
注意:这里我们需要判断两个日期谁大谁小。
//日期-日期 d1 - d2
int Date::operator-(const Date& d)
{
int flag = 1;//flag为1:表示返回正值
//假设
Date max = *this;
Date min = d;
if (*this < d)//假设不成立
{
max = d;//更正min、max
min = *this;
flag = -1;//假设错误,表示返回负值,返回的总天数*flag就可以得到正数的两天差差值
}
int n = 0;//相当于一个计数器
while (min != max)
{
++min;//开始从min数到max
++n;//开始计数
}
return n * flag;//返回的正差值天数
}
八、流插入和流提取
scanf
、printf
不能输入输出自定义类型例如类类型,间接输入输出又不方便,直接确定的是内置类型。
根据下面两图发现:cout
是ostream
类型的对象,cin
是istream
类型的对象。内置类型能够直接使用是因为istream
、ostream
里面已经用operator
重载好这些操作符了,他们可以自动识别类型(如内置类型)都是因为函数重载。
如果想自主输入日期和提取日期,就可以自己实现重载<<
和>>
操作符。
8.1 流插入运算符重载
8.1.1 实现自主输出
如果想要自己重载函数实现下面的效果:输出d1到控制台。
cout << d1;//日期类对象流入控制台(I/O流)
- 第一种写法(错误写法):写成
成员函数
。
//cout是ostream类型的对象
void Date::operator<<(ostream& out)
{
out << _year << "年" << _month << "月" << _day << "日" << endl;
}
- 报错结果:
//test.cpp
void TestDate()
{
Date d1, d2;
cout << d1;//error:这里为什么用不了?
//error: message : 尝试匹配参数列表“(std::ostream, Date)”时。
}
- 分析报错原因:参数无法匹配:
cout
本来应该匹配ostream
类,但是成员函数第一个隐含的参数是this
指针也就是日期类,d1
本来应该匹配日期类,现在匹配的是ostream
类。 - 改进:根据上面的分析,做了些许
test.cpp
文件的改进。虽然可以运行但是现在这段代码和我们本来的意义背道而驰。而且this指针又因const
保护无法变动,所以舍弃成员函数的方法。
void TestDate5()
{
Date d1, d2;
//cout << d1;
d1 << cout;
d1.operator<<(cout);
}
- 第二种写法:
- 写成
全局函数
,为了访问Date类里面的成员变量,我们可以将private
换成public
或者 提供get函数。
//测试全局函数operator<<
void TestDate6()
{
Date d1, d2;
cout << d1;
operator<<(cout, d1);
}
- 在类外面访问类里面的内容,最好的办法就是加一个友元函数声明
**friend**
,位置随意。
//Date.h
//在类里面加入friend,友元函数声明
friend void operator<<(ostream& out, const Date& d);//d加const是因为d不能被改变
//在类外面
void operator<<(ostream& out, const Date& d);
//这样就可以在类外面访问类里面的私有成员了
//Date.cpp
void operator<<(ostream& out, const Date& d)//全局函数
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}
8.1.2 实现自主连续输出
如果想要实现连续输出,就像下面一样的效果:
cout << d1 << d2;//需要从左往右结合
// cout << d1,返回左操作数cout,cout << d2,整体表达式的返回值就是左操作数cout
// 所以全局函数的返回值用 ostream& 来接收
根据前面的代码和逻辑,可以类比地写出本段代码如下:
//Date.h
//在类里面加入friend,友元函数声明
friend ostream& operator<<(ostream& out, const Date& d);
//在类外面
ostream& operator<<(ostream& o2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ut, const Date& d);
//这样就可以在类外面访问类里面的私有成员了
//Date.cpp
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
//本段代码的思路总结:
//写多少个operator就是多少个全局函数调用,
//全局函数里面需要调用类里面的私有成员变量,
//因此我们就是用了友元函数声明
8.2 流提取运算符重载
//流提取运算符重载
//Date.h
//在类里面加入friend,友元函数声明
friend istream& operator>>(istream& in, Date& d);//d不加const:流对象不支持拷贝构造
//在类外面
istream& operator>>(istream& in, Date& d);
//Date.cpp
istream& operator>>(istream& in, Date& d)
{
//改进:检查是否为非法日期
while(1)//进入循环,先检查日期是否非法,若非法,则重新输入
{
cout << "请依次输入年月日:";//流提取里面使用cout这样是不会乱的
in >> d._year >> d._month >> d._day;//不用cin,避免流错乱
if(!d.CheckDate())
{
cout<<"输入日期非法:";
d.Print();
cout<<"请重新输入!!!"<<endl;
}
else
{
break;
}
}
return in;
}
在此之前,我们就知道cin
和cout
是有绑定关系的,因为C++兼容C语言,缓冲区就会保持同步,所以在进行刷新的时候会把printf
的缓冲区进行刷新,cout
和printf
多次混用就不会乱。所以:如果printf
没有进行刷新,那么cout
就在刷新自己之前先刷新printf
。
cin
和cout
也是同理,在cin
进行I/O操作之前会将cout
的缓冲区进行刷新。
但是刷新缓冲区会牺牲效率,刷新缓冲区也会有一定的要求,例如遇到换行符会主动进行刷新或者程序结束时会刷新。
如果在IO
需求比较高的地方,就可以加入三行代码提高C++的IO效率。
#include <iostream>
using namespace std;
int main()
{
// 在IO需求比较高的地方,比如部分大量输入的竞赛题中,
// 加上以下的三行代码,就可以提高C++的IO效率。
ios_base::sync_with_stdio(false);//关闭C语言的环境
cin.tie(nullptr);//原本默认cin是与cout进行绑定的,nullptr使得cin和谁都不进行绑定
cout.tie(nullptr);//cout也与谁都不你行绑定了
return 0;
}
九、所有代码
Date.h
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
//友元函数声明
friend ostream& operator<<(ostream& out, coonst Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
bool CheckDate() const;
void Print() const;
//默认是inline
//获取具体某一年某一月的天数
int GetMonthDay(int year, int month) const
{
assert(month > 0 && month < 13);
// 因为该函数会经常调用,但是数组的值一直是不需要变化的,因此可以使用静态数组
// 好处是在静态区只会创建一份变量
static int GetMonthDayArray[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
//先判断是否为2月
{
return 29;//闰年
}
return GetMonthDayArray[month];
}
// 构造函数
//Date(int year = 1900, int month = 1,int day = 1);
Date(int year, int month, int day);
// 拷贝构造函数
// d2(d1)
Date(const Date& d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
//比较大小可以加const,因为不修改成员变量
// >运算符重载
bool operator>(const Date& d) const;
// ==运算符重载
bool operator==(const Date& d) const;
// >=运算符重载
bool operator >= (const Date& d) const;
// <运算符重载
bool operator < (const Date& d) const;
// <=运算符重载
bool operator <= (const Date& d) const;
// !=运算符重载
bool operator != (const Date& d) const;
// 操作赋值操作符
Date& operator=(const Date& d);
// 日期+=天数
Date& operator+=(int day);
// 日期+天数
Date operator+(int day) const;
// 日期-天数
Date operator-(int day) const;
// 日期-=天数
Date& operator-=(int day);
//不加const,因为需要成员变量本身的改变
// 前置++
Date& operator++();
// 后置++
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
// 日期-日期 返回天数
int operator-(const Date& d) const;
private:
int _year;
int _month;
int _day;
};
//只要不修改成员变量的都建议加上const
7月13日讲了
Date.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "Date.h"
bool Date::CheckDate() const
{
if(_month < 1||_month > 12
|| _day < 1 || _day > GetMonthDay(_year ,_month))
{
return false;
}
else
{
return true;
}
}
// 构造函数
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
if(!CheckDate())//判断日期是否合法
{
cout<<"非法日期:";
Print();
}
}
// 拷贝构造函数
// d2(d1)
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
// >运算符重载
bool Date::operator>(const Date& d) const
{
if (_year > d._year)
return true;
else if (_year == d._year)
{
if (_month > d._month)
return true;
else if (_month == d._month)
{
if (_day > d._day)
return true;
}
}
return false;
}
// ==运算符重载
bool Date::operator==(const Date& d) const
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
// >=运算符重载
bool Date::operator >= (const Date& d) const
{
return *this > d || *this == d;
}
// <运算符重载
bool Date::operator < (const Date& d) const
{
return !(*this >= d);
}
// <=运算符重载
bool Date::operator <= (const Date& d) const
{
return !(*this > d);
}
// !=运算符重载
bool Date::operator != (const Date& d) const
{
return !(*this == d);
}
// 日期+=天数
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;
}
// 日期+天数 ---使用前面实现的+=运算符重载实现
//Date Date::operator+(int day) const
//{
// Date temp(*this);
// temp += day;
// return temp;
//}
// 日期+天数 ---直接实现
Date Date::operator+(int day) const
{
Date temp(*this);
temp._day += day;
while (temp._day > GetMonthDay(_year, _month))
{
temp._day -= GetMonthDay(temp._year, temp._month);
++temp._month;
if (temp._month == 13)
{
++temp._year;
temp._month = 1;
}
}
return temp;
}
// 日期-=天数
Date& Date::operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
// 日期-天数 ---使用前面-=运算符重载实现
Date Date::operator-(int day) const
{
//Date temp(*this);
Date temp(*this);
temp -= day;
return temp;
}
//日期-天数 ---直接实现
//Date Date::operator-(int day) const
//{
// //Date temp(*this);
// Date temp(*this);
// temp._day -= day;
// while (temp._day <= 0)
// {
// --temp._month;
// if (temp._month == 0)
// {
// --temp._year;
// temp._month = 12;
// }
// temp._day += GetMonthDay(temp._year, temp._month);
// }
// return temp;
//}
// 前置++
Date& Date::operator++()
{
*this += 1;
return *this;
}
// 后置++
Date Date::operator++(int)
{
Date temp(*this);
*this += 1;
return temp;
}
// 后置--
Date Date::operator--(int)
{
Date temp(*this);
*this -= 1;
return temp;
}
// 前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
// 日期-日期 返回天数
int Date::operator-(const Date& d) const
{
int flag = 1;
Date max = *this;
Date min = d;
if (*this < d)
{
flag = -1;
max = d;
min = *this;
}
int n = 0;
while (min != max)
{
min++;
n++;
}
return n * flag;
}
喜欢的uu记得三连支持一下哦!