Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法…感兴趣就关注我吧!你定不会失望。
0.运算符重载
C++中为了增强代码的可读性,加入了运算符的重载,与其他函数重载一样。其命名格式如下:
返回值类型 operator操作符(参数列表)
Date operator<(Date&d1)
但并不是所有运算符都可以重载的:
1.不可以自定义出一个全新的操作符,如@
2..* :: sizeof ?: . 以上操作符不能重载,特别是第一个.*
接下来我们定义一个日期类来作为操作符重载的实践:
class Date{ public: Date(int day=1,int month=1,int year=1) { if(month>0&&month<13&&day>0&&day<getmonth(year,month)) _day=day,_month=month,_year=year; else cout<<"输入错误"; } Date(const Date&d) { _day=d._day; _month=d._month; _year=d._year; cout<<"copy"; } private: int _year; int _month; int _day; }
这是一个最基本的类函数,其包括一个默认构造函数,以及一个拷贝构造函数,拷贝构造函数会在调用时输出:“copy”,这将有助于我们后期分析函数.
1.赋值运算符 = 重载
先来看看他是怎么定义的吧.同样符合上方的定义法则:
Date& operator=(const Date&d) { _day=d._day; _month=d._month; _year=d._year; return *this; }
为什么需要返回值调用呢?因为这样做可以节省空间。以引用的方式传参、返回值不需要调用拷贝构造函数
那this不是形式参数嘛?为什么出作用域还能被引用呢?*this本身是形式参数,但是 this指向的是date对象,其栈帧存储在Main函数中,所以this会被销毁,而返回的是this指向的对象,并不会被销毁
加入const只是一个保险,让我们不会改变传入的参数
我们可以以一段代码来演示一下:
class Date{ public: Date(int day=1,int month=1,int year=1) { if(month>0&&month<13&&day>0&&day<getmonth(year,month)) _day=day,_month=month,_year=year; else cout<<"输入错误"; } Date(const Date &d) { _day=d._day; _month=d._month; _year=d._year; cout<<"copy"; } Date& operator=(const Date &d) { _day=d._day; _month=d._month; _year=d._year; return *this; } int main() { Date d1(2,5,2024); Date d2; d2=d1; }
先将两个引用都去掉,运行会发现,调用了两次拷贝构造,分别为传值以及返回值:
**加上后则不会调用拷贝构造,所以这样写节省空间与时间。**在编译阶段,改代码会被转变成
d2.operator=(d1)
这也方便我们对这个操作符的具体执行方式进行理解。
至于为什么需要返回Date类型呢?考虑以下场景:
int a; int b; int c; a=b=c=1;
这段代码运行的实质为:
所以我们需要令其返回Date类型来满足这种情况:
int main() { Date d1(2,5,2024); Date d2; Date d3; d3=d2=d1; }
赋值运算符如果不自己定义,系统就会自动生成一个赋值运算符(这与之前提到的构造函数、拷贝构造函数、析构函数),其调用规则业余之前一样,若没有定义则内置类型完成浅拷贝,自定义类型调用其自定义的赋值运算符重载,所以与调用拷贝构造函数相同,涉及空间管理的内置类型,我们需要自己定义拷贝构造函数。这也导致赋值运算符不能定义在全局,只能定义在类中,若在自己在全局定义一个就会和类中定义的冲突。
2.比较运算符 == 重载
bool operator==(const Date&d)const { if(_year==d._year&&_month==d._month&&_day==d._day) { return true; } return false; }
三个都相同则返回true,否则返回false,这整体逻辑很好理解。这个函数并不是默认函数,所以我们可以把它定义在全局,也可以把他定义在类中。
若定义在全局会出现类中私有成员无法访问的问题(这之后会解决,但现在我们为了规避这个问题我们先将其放在类中)
那这个const为什么放在外面呢?这里是对this指针,也就是本体的一个修饰,因为this指针是隐式的存在,且在这个重载中我们并不会改变本体,所以对其加上修饰
this指针原来的模样:无法改变this指针指向的内容,但可以改变其值
Date * const this
现在修饰后:既无法改变this指针指向的内容,也无法改变其值,对其起到了一个保护的作用
const Date * const this
3.比较运算符 != 重载
bool operator!=(const Date&d)const { if(*this==d)return false; return true; }
这里直接复用了上方的结果,也证明了重载运算符的强大之处hhhh
4.比较运算符 < 重载
bool operator<(const Date &d)const { 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; }
主要为逻辑的实现:
1.若年小,则直接返回true
2.若年相同,月小,直接返回true
3.若年月都相同,天数小,直接返回true
若不满足以上情况,则说明不小于,则直接返回false
5.比较运算符 <= 重载
bool operator<=(const Date &d)const { return *this<d||*this==d; }
也是对之前写过的==以及<的一个复用.若小于或者等于则满足小于等于
6. 比较运算符 > 重载
bool operator>(const Date &d)const { return (!(*this<d)&&(*this!=d))?true:false; }
若不小于且不等于则满足小于等于
7.比较运算符 >= 重载
bool operator>=(const Date &d)const { return !(*this<d); }
若不小于则满足大于等于
8. 赋值运算符 += 与 + 重载
在实现接下来的重载中,为了方便获得每一个月份的具体日期,包括闰年平年的判断。在类中实现了一个getmonth函数用来返回每一个月具体天数。
int getmonth(int year,int month) { static int monthday[13]={0,31,28,31,30,31,30,31,31,30,31,30,31}; if((year%4==0&&year%100!=0)||year%400==0) { if(month==2)return 29; } return monthday[month]; }
传入年与月,进行判断具体日期数,其中闰年满足四年一闰,百年不闰,四百年一闰
接下来进行+=的实现:这里需要先判断下若Day<0,则等价于计算*this-=Day
Date& operator+=(int Day) { if(Day<0) { *this-=(-Day); return *this; } _day+=Day; while(_day>getmonth(_year,_month)) { _day-=getmonth(_year,_month); ++_month; if(_month>12) { _month=1; _year++; } } return *this; }
具体实现逻辑如下:
那么我们来看看+的逻辑:
Date operator+(int day) { Date tmp=*this; tmp+=day; tmp.print(); return tmp; }
为了不改变Date对象的本身,需要先将Date复制一份(这里使用拷贝构造函数,而不是赋值运算符重载,因为其在初始化对象时赋值),然后复用+=,最后返回其本身.因为是局部参数,所以并不能返回引用,因为tmp出作用域会被销毁.
9.赋值运算符 -= 与 - 重载:
-=:
Date& operator-=(const int Day) { if(Day<0) { *this+=(-Day); return *this; } _day-=Day; while(_day<=0) { --_month; if(_month<1) { _year--; _month=12; } _day+=getmonth(_year,_month); } return *this; }
-:
Date operator-(const int Day) { Date tmp=*this; return tmp-=Day; }
整体逻辑与加法大差不差,不过多赘述.
10. 前置++与后置++
前置++
Date& operator++() { *this+=1; return *this; }
若我想要将一个Date对象执行++操作,则直接:++Date即可.
后置++:为了和前置++再一次发生重载,加入了一个参数,这个参数在重载过程中并不会起到什么作用,仅为分辨具体是哪个重载,使用时编译器会自己在后置++中传入0,我们仅需正常使用即可,
Date operator++(int) { Date tmp=*this; *this+=1; return tmp; }
在刚学习c的阶段,总会看到讨论关于前置++与后置++的讨论,现在看重载就知道了,因为后置++会拷贝一份原始值,所以效率会低一些.
不过在内置类型中,这种效率变换可以忽略不计.
11.前置–与后置–
Date& operator--() { *this-=1; return *this; } Date operator--(int) { Date tmp; *this-=1; return tmp; }
12.逻辑运算符-的重载
为两个日期类间想减,计算日期间的差值
int operator-(Date &d) { Date max=*this; Date min=d; int flag=1; if(max<min) { max=d; min=*this; flag=-1; } int n=0; while(min!=max) { ++min; ++n; } return n*flag; }
先找出最大的日期,通过将最小的++与计数天数++,一步步逼近最大的天数,最后返回,注意传值需要传引用,提高效率
13.流运算符重载
为什么在c++中cout/cin可以输出/输入任意内置类型的对象,而不用带对象类型呢?因为在c++中cout与cin也为一个流对象,其底层实现了多个类型的重载
此重载需要定义在全局中,若定义在类中,根据上方的转换法则,则会变成d1<<cout,但定义在全局中,会出现访问不了私有变量的情况,所以我们提供了一个友元函数的声明.
friend ostream& operator<<(ostream& out,const Date&d); friend istream& operator>>(istream& in, Date&d);
放在类中哪里都可以,表示这个函数是这个类的朋友,可以直接访问其中的变量与函数(关于友元的具体含义之后会细讲,)
13.1输出流重载:
ostream& operator<<(ostream& out,const Date&d) { out<<"day: "<<d._day<<" month: "<<d._month<<" year: "<<d._year<<endl; return out; }
ostream为输出流对象,其返回值会放进输出流在输出
13.2输入流重载:
istream& operator>>(istream& in,Date&d) { int year, month, day; in >> day >> month >>year; if (month > 0 && month < 13 && day > 0 && day <= d.getmonth(year, month)) { d._year = year; d._month = month; d._day = day; } else { cout << "非法日期" << endl; } return in; }
整体与上方相同
14.完整代码:
#include<iostream> using namespace std; class Date{ public: friend ostream& operator<<(ostream& out,const Date&d); friend istream& operator>>(istream& in, Date&d); Date(int day=1,int month=1,int year=1) { if(month>0&&month<13&&day>0&&day<getmonth(year,month)) _day=day,_month=month,_year=year; else cout<<"输入错误"; } Date(const Date&d) { _day=d._day; _month=d._month; _year=d._year; cout<<"copy"<<endl; } int getmonth(int year,int month) { static int monthday[13]={0,31,28,31,30,31,30,31,31,30,31,30,31}; if((year%4==0&&year%100!=0)||year%400==0) { if(month==2)return 29; } return monthday[month]; } void print() { cout<<"day: "<<_day<<" month: "<<_month<<" year: "<<_year<<endl; } Date operator=(const Date d) { _day=d._day; _month=d._month; _year=d._year; return *this; } Date& operator+=(int Day) { if(Day<0) { *this-=(-Day); return *this; } _day+=Day; while(_day>getmonth(_year,_month)) { _day-=getmonth(_year,_month); ++_month; if(_month>12) { _month=1; _year++; } } return *this; } Date operator+(int day) { Date tmp=*this; tmp+=day; tmp.print(); return tmp; } Date& operator++() { *this+=1; return *this; } Date operator++(int) { Date tmp=*this; *this+=1; return tmp; } int operator-(Date &d) { Date max=*this; Date min=d; int flag=1; if(max<min) { max=d; min=*this; flag=-1; } int n=0; while(min!=max) { ++min; ++n; } return n*flag; } Date& operator-=(const int Day) { if(Day<0) { *this+=(-Day); return *this; } _day-=Day; while(_day<=0) { --_month; if(_month<1) { _year--; _month=12; } _day+=getmonth(_year,_month); } return *this; } Date operator-(const int Day) { Date tmp=*this; return tmp-=Day; } Date& operator--() { *this-=1; return *this; } Date operator--(int) { Date tmp; *this-=1; return tmp; } bool operator==(const Date&d)const { if(_year==d._year&&_month==d._month&&_day==d._day) { return true; } return false; } bool operator!=(const Date&d)const { if(*this==d)return false; return true; } bool operator<(const Date &d)const { 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 operator<=(const Date &d)const { return *this<d||*this==d; } bool operator>(const Date &d)const { return (!(*this<d)&&(*this!=d))?true:false; } bool operator>=(const Date &d)const { return !(*this<d); } private: int _year; int _month; int _day; }; ostream& operator<<(ostream& out,const Date&d) { out<<"day: "<<d._day<<" month: "<<d._month<<" year: "<<d._year<<endl; return out; } istream& operator>>(istream& in,Date&d) { int year, month, day; in >> day >> month >>year; if (month > 0 && month < 13 && day > 0 && day <= d.getmonth(year, month)) { d._year = year; d._month = month; d._day = day; } else { cout << "非法日期" << endl; } return in; } int main() { Date d1(2,5,2024); d1+=100; d1.print(); }
至此日期类整体完成
15. 取地址运算符重载
class Date { public : Date* operator&() { return this ; } const Date* operator&()const { return this ; } private : int _year ; int _month ; int _day ; };
默认情况下,编译器会自动生成这个重载,大多数情况下我们也不需要写这个重载.
若不想让一个人获得可修改地址,仅想让其获得不可修改的const地址,可以这样写
class Date { public : Date* operator&() { return NULL ; } const Date* operator&()const { return this ; } private : int _year ; int _month ; int _day ; };
16.至此六个内置类型成员函数完结