文章目录
一,Date.h
二,Date.cpp
三,解析
[1 构造函数和打印函数](#1 构造函数和打印函数)
[2 类比较大小](#2 类比较大小)
[3 `+=` 运算符重载函数](#3 += 运算符重载函数)
[`+` 运算符重载函数](#+ 运算符重载函数)
[4 `-=`运算符重载函数](#4 -=运算符重载函数)
[5 前置++和后置++运算符重载函数](#5 前置++和后置++运算符重载函数)
[6 前置 -- 和后置 -- 运算符重载函数](#6 前置 -- 和后置 -- 运算符重载函数)
[7 日期减日期](#7 日期减日期)
[8 GetMonthDay](#8 GetMonthDay)
[9 重载 流插入<< 和 流提取>>](#9 重载 流插入<< 和 流提取>>)
一,Date.h
#include<iostream>
#include<assert.h>
class Date
{
//全局函数声明友元才能访问成员变量
friend std::ostream& operator<<(std::ostream& out, const Date& d);
friend std::istream& operator>>(std::istream& in, Date& d);
public:
Date(int year = 1970, int month = 1, int day = 1);//构造函数
void Print();//打印
//直接定义类里面,默认是inline,频繁调用,设置为内联
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
static int monthDayArray[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))
{
return 29;
}
else
{
return monthDayArray[month];
}
}
bool CheckDate();
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);
Date& operator+=(int day);
Date operator+(int day);
Date& operator-=(int day);
Date operator-(int day);
Date& operator++();
Date operator++(int);
Date& operator--();
Date operator--(int);
int operator-(const Date& date);
private:
int _year;
int _month;
int _day;
};
//运算符重载函数,函数重载
std::ostream& operator<<(std::ostream& out, const Date& d);
std::istream& operator>>(std::istream& in, Date& d);
二,Date.cpp
#include"Date.h"
bool Date::CheckDate()
{
if(_month >12 || _month<1 &&
_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)
{
std::cout << "日期错误" << std::endl;
}
}
void Date::Print()
{
std::cout << _year << " " << _month << " " << _day << std::endl;
}
bool Date::operator<(const Date& d)
{
if (_year < d._year)
{
return true;
}
else if (_year == d._year)
{
if (_month < d._month)
{
return true;
}
else if (_month == d._month)
{
return _day < d._day;
}
}
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+=(int day)
{
if (day < 0)
{
return *this -= (-day);
}
_day += day;
while(_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_month = 1;
_year++;
}
}
return *this;
}
Date Date::operator+(int day)
{
Date tmp = *this;
tmp += day;
return tmp;
}
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += (-day);
}
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator-(int day)
{
Date tmp = *this;
tmp -= day;
return tmp;
}
Date& Date::operator++()
{
return *this += 1;
}
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
Date& Date::operator--()
{
return *this -= 1;
}
Date Date::operator--(int)
{
Date tmp = *this;
*this -= 1;
return tmp;
}
int Date::operator-(const Date& date)
{
Date max = *this;
Date min = date;
int flag = 1;
if (*this < date)
{
max = date;
min = *this;
flag = -1;
}
int n = 0;
while (max != min)
{
++min;
++n;
}
return flag * n;
}
std::ostream& operator<<(std::ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << std::endl;
return out;
}
std::istream& operator>>(std::istream& in, Date& d)
{
if (!d.CheckDate())
{
std::cout << "日期错误" << std::endl;
}
in >> d._year >> d._month >> d._day;
return in;
}
三,解析
1 构造函数和打印函数
.h文件
Date(int year = 1970, int month = 1, int day = 1);//构造函数
void Print();//打印
.c文件
Date::Date(int year, int month, int day )
{
_year = year;
_month = month;
_day = day;
if (!CheckDate)
{
std::cout << "日期错误" << std::endl;
}
}
void Date::Print()
{
std::cout << _year << " " << _month << " " << _day << std::endl;
}
❗❗❗唯一要注意的地方就是全缺省函数声明和定义分离的时候,缺省值只能写在声明处
2 类比较大小
.h文件
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);
.c文件
bool Date::operator<(const Date& d)
{
if (_year < d._year)
{
return true;
}
else if (_year == d._year)
{
if (_month < d._month)
{
return true;
}
else if (_month == d._month)
{
return _day < d._day;
}
}
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);
}
❗❗❗类比较大小应用的是运算符重载函数,而> >= < <= == != 六个运算符中,只需要写出> 和 == 或者 < 和==`,因为其他的运算符重载函数内部通过这两个运算符重载函数组合,都可以实现
<运算符重载函数
bool Date::operator<(const Date& d)
{
if (_year < d._year)
{
return true;
}
else if (_year == d._year)
{
if (_month < d._month)
{
return true;
}
else if (_month == d._month)
{
return _day < d._day;
}
}
return false;
}
两个操作数,第一个操作数就是调用成员函数的对象,形参是隐藏的this,第二个操作数就是传进来的参数,if else 判断的逻辑是,如果_year小则小,则返回true,否则,如果_year相等的情况下,如果_month小则小,则返回true,最后如果在_month相等的情况下,直接返回_day比较的结果,因为最后只剩下_day了,在前面都相等的前提下_day的结果就是整体的结果,但是前面的不能直接返回比较结果,要一个个判断,比如不能直接return _month < _month 因为_month后面还有_day,如果_month 相等的情况下还要比较_day,而return 会直接renturn false就不会去考虑_day了,最后_year 和 _month 判断的都是返回ture的情况,如果不符合判断条件则直接跳出if else 去 return false
==运算符重载函数
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
全等的话则必须_year 和 _month 和 _day 都要同时全等
> 和 == 组合成其他运算符重载函数
<=
return *this < d || *this == d;
>
return !(*this <= d);
>=
return !(*this < d);
!=
return !(*this == d);
其余的运算符重载函数都可以通过调用>或者==运算符重载函数组合实现
3 += 运算符重载函数
Date& Date::operator+=(int day)
{
if (day < 0)
{
return *this -= (-day);
}
_day += day;
while(_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_month = 1;
_year++;
}
}
return *this;
}
+=和+的区别就是+=会改变操作数的大小,而+中的操作数只是用来运算,日期类+法的逻辑是先把需要+的天数加上,然后在while循环中不断将对应_month中对应的_day转化成_month进位,_month满12进位到_year,然后_month重置为1即可,GetMonthDay函数可以通过_year和_month计算出_month对应的天数,所以只要_day不匹配当前的_month的_day也就是>就一直循环执行-对应的_day进位到_month然后判断_month是否要进位到_year并重置_month,最后return对象*this
if (day < 0)判断如果输入负数的情况,+ -1等价于- 1所以直接return *this -= (-day)即可-1中-等价于-=而1等价于-day
+ 运算符重载函数
Date Date::operator+(int day)
{
Date tmp = *this;
tmp += day;
return tmp;
}
+的逻辑就是不改变操作数的大小,但是结果要是改变好的,我们做法是把调用成员的实例化对象拷贝到Dare tmp 然后用tmp调用+=运算符重载,最后return tmp
4 -=运算符重载函数
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += (-day);
}
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_month = 12;
_year--;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
-=的逻辑是借位,先直接-掉day得到_day,while循环判断_day是否<= 0,如果<= 0则进入while循环中循环执行_month-1然后直接判断_month是否== 0,如果_month == 0则需要借位重置为12,然后_year --,这样的话就可以最开始的_month--通过if语句判断过后就可以得到正确的月份,然后将这个_month的_day全部加到当前_day中,最后一直循环这些步骤,最后直到_day为正符合正常_day即可。注意顺序不能乱,因为必须通过if判断才能得到正确的_month得到正确的_month才能得到正确的_day
if(day<0)的话,-(-1) == + 1所以直接调用+= (-day) 其中+=等于+,1等于(-day)
-运算符重载函数
Date Date::operator-(int day)
{
Date tmp = *this;
tmp -= day;
return tmp;
}
-运算符和-=的区别在于-运算符不改变操作数,所以先把*this拷贝到Date tmp中,然后Date tmp调用+=运算符重载函数,最后return tmp
5 前置++和后置++运算符重载函数
Date& operator++();//前置++声明
Date& Date::operator++()//前置++定义
{
return *this += 1;
}
Date operator++(int);//后置++声明
Date Date::operator++(int)//后置++定义
{
Date tmp = *this;
*this += 1;
return tmp;
}
前置++和 后置++ 是两个功能不同的函数,但是前置++ 和 后置++ 这两个运算符重载函数的函数名是相同的,都是operator++都是单操作数。为了区分这两个功能不同但是名字完全相同的运算符函数,让两个函数构成函数重载,规定了后置++ 函数定义和函数声明强行增加了一个int形参,和前置++运算符重载函数构造函数重载
前置++函数体中直接用*this += 1即可,因为前置++效果是先++然后返回++后的结果。返回值类型可以应用
后置++函数体中需要将*this拷贝到Date tmp中,将*this+=1然后return tmp因为后置++的效果是把当前值返回之后,再进行++
d++就是调用后置++,++d就是调用前置++,如果显示调用的话d.operator++()就是调用前置++,d.operator++(1)就是调用后置++
不需要去在意形参int只是为了让前置++和后置++这两个同名的运算符重载函数构造函数重载
6 前置 -- 和后置 -- 运算符重载函数
Date& operator--();//前置--声明
Date& Date::operator--()//前置--定义
{
return *this -= 1;
}
Date& operator--(int);//后置--声明
Date Date::operator--(int)//后置--定义
{
Date tmp = *this;
*this -= 1;
return tmp;
}
7 日期减日期
int Date::operator-(const Date& date)
{
Date max = *this;
Date min = date;
int flag = 1;
if (*this < date)
{
max = date;
min = *this;
flag = -1;
}
int n = 0;
while (max != min)
{
++min;
++n;
}
return flag * n;
}
日期 - 日期的主要逻辑是,通过判断找出小的哪个日期,然后在while中设置计算器n在小日期追赶大日期的时候用计算器n计算差值。所以需要先拷贝Date max = *this和Date min = date因为不能改变原本的日期,然后先假设一个max和min,然后用if语句进行筛选,再次进行假设,设置和第一次假设相反的判断条件,如果符合i f的假设就进入if修改max 和 min同时把flag取反,因为我们原本设置的是*this 大于 date也就是第一个操作数大于第二个操作数最后差值为正,但是如果进入if之后则说明*this小于date,第一个操作数小于第二个操作数,差值为-,所以flag取反,最后在while中min追赶max同时计数器n计数,由于计算器n得出来的数一定是正数,所以return flag * n
8 GetMonthDay
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
static int monthDayArray[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))
{
return 29;
}
else
{
return monthDayArray[month];
}
}
通过日期类实例化对象的_year和_month来计算对应的天数需要考虑的因素有闰年的2月29天,先设置monthDayArray数组把对应_month对应的天数设置进去,最后用下标引用操作符把month设置为操作数,这样就可以实现对应的month返回对应的天数了。但是我们要单独判断闰年的2月,所以 if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))判断闰年,month == 2需要放在前面,因为只有2月的天数才受到闰年的影响。
类中定义的函数默认是内联,GetMonthDay经常被调用,设置成内联减少开栈帧的消耗,直接在调用处就可以拿到定义了,内联函数声明和定义不能分离到不同的文件中,因为内联函数没有函数栈帧,不进符号表,分离会导致链接错误
static修饰变量会让变量到静态区这样就不需要频繁的创建数组
9 重载 流插入<< 和 流提取>>
<<流插入和>>流提取本质上是运算符重载函数,而调用这个运算符重载函数的,是全局对象cout和全局对象cin这两个全局对象调用它们类中的成员函数operator<<和operator >>,而这两个全局对象cout和cin是类ostream和类istream实例化的全局对象,流插入和流提取是类中的成员函数,而流插入operator<<()和流提取operator>>()这两个运算符重载函数实现的都是参数为内置类型的函数重载,所以我们使用cout和cin的时候才可以写任意类型,不用想printf和scanf函数一样指定类型,因为写任意类型的时候,会调用对应的运算符重载函数,因为不同参数的流插入和流提取运算符重载函数构成了函数重载
ostream& ostream::operator<<(const Date& d){}
这种写法无法实现,因为这种写法再一次重载了流插入运算符重载函数,把参数调整为自定义类型,但是如果依然是对类ostream的全局对象cout的成员函数operator<<()进行函数重载的话,就必须把在类ostream中进行实现,只有在类ostream中实现的成员函数,才能由类ostream实例化的对象cout调用,但是这样要修改类ostream,修改源文件是不可能的
函数声明:
Date& operator<<(ostream& out);
函数定义:
Date& Date::operator<<(ostream& out)
{
out<<_year<<"年"<<_month<<"月"<<_day<<"日";
return *this;
}
函数调用:d<<out 或者 d.operator<<(cout)
d.operator<<(cout)).operator<<(x);
这种写法和做法是在Date类中声明定义成员函数operator<<,形参为ostream类实例化对象cout,把cout作为参数传进成员函数中,打印自定义类型Date中的内置类型,因为打印自定义类型的本质就是打印自定义类型中的内置类型,因为自定义类型本质就是由内置类型组成。最后return *this是为了可以重复调用函数,返回值为return *this的话,函数的返回值依然是日期类的实例化对象,调用完第一次运算符重载函数之后,可以继续调用运算符重载函数,因为只要对象才能调用类中的成员函数,而返回值一直都是对象,所以可以连续调用
这种写法不好的地方在于,运算符重载函数使用的时候,调用成员函数的对象要在左边,参数要写在右边,操作数的顺序就是形参的顺序,因为肯定是对象调用成员函数,这样就会导致d << cout的形式,这样的写法不符合我们日常使用cout的习惯
经过上面的种种错误写法,都是将operatro<<运算符重载函数重载为成员函数(成员函数才能访问类中的私有内置类型成员变量),而且本质都是必须将ostream类的实例化对象进行函数体中,因为最终自定义类型中的打印操作还是靠的是cout打印内置类型,因为自定义类型中最终都是由内置类型组成的,所以我们正确的写法是重载成全局函数,但是这样的话我们就无法访问私有内置类型成员变量,由此我们引入了友元的概念
全局函数无法访问类中私有的成员,但是如果把全局函数在类中进行友元声明,就可以让全局函数访问到类中的私有成员变量,语法形式就是friend + 函数声明在类中就是友元声明
流插入运算符重载函数函数重载形式声明
std::ostream& operator<<(std::ostream& cout,const Date& d);
流插入运算符重载函数函数重载形式定义
std::ostream& operator<<(std::ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << std::endl;
return out;
}
流提取运算符重载函数函数重载形式声明
std::istream& operator>>(std::istream& in, Date& d);
流提取运算符重载函数函数重载形式定义
std::istream& operator>>(std::istream& in, Date& d)
{
if (!d.CheckDate())
{
std::cout << "日期错误" << std::endl;
}
in >> d._year >> d._month >> d._day;
return in;
}
运算符重载函数中操作数的左右顺序就是形参的左右顺序,所以cout << d的形式的话,形参左右顺序为ostream& out 和 const Date& d,renturn out让函数可以复用,cout<<d1<<d2从左到右执行运算符重载函数,第一个运算符重载函数return的结果是cout就可以继续和d2构成运算符重载函数的两个形参,而通过友元,就可以访问到类中的私有成员了。