文章目录
一、【C++】赋值运算符重载1.1 运算符重载【引入】1.2 运算符重载1.3 赋值运算符重载1.4 赋值 二、日期类的实现2.1 判断小于2.2 判断等于2.3 判断小于等于2.4 判断大于2.5 判断大于等于2.6 判断不等于2.7 日期加等天数2.8 获取月份天数2.9 日期加天数2.9.1 日期减等天数2.9.2 日期减天数 三、前置++ && 后置++3.1 日期减日期【返回天数】3.2 流插入3.3 流提取3.4 检查输入日期是否合法 四、日期类的实现【源码】五、const修饰5.1 const成员函数5.2 小结一下:5.3 默认成员函数【取地址及const取地址操作符重载】
一、【C++】赋值运算符重载
1.1 运算符重载【引入】
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。函数名字为:关键字operator后面接需要重载的运算符符号。函数原型:返回值类型 operator操作符(参数列表)注意:
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型参数【不能去重载运算符改变内置类型的行为】
用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少1个,因为成员函数的第一个参数为隐藏的this
.* :: sizeof ?: .
注意以上5个运算符不能重载。这里的是.*
,不是*
,这写经常在笔试选择题中出现。
我们写了一个日期类,有没有可能要比较比较呢?
class Date{public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private: // 一会有解决方法int _year;int _month;int _day;};
我们写了一个比较相等的函数,如果是传值的话没有必要,我们形参直接写成&
,而且也不需要修改,所以再加上const
bool DateEquel(const Date& x, Date& y){return x._year == y._year&& x._month == y._month&& x._day == y._day;}
下面再实现一个函数【比较小于】 bool DateLess(const Date& x, Date& y){if (x._year < y._year){return true;}else if(x._year == y._year){if (x._month < y._month){return true;}else if(x._month == y._month){if (x._day < y._day){return true;}}}return false;}
上面有一些不好的地方,取名字的问题,取得很乱就不知道这个函数是干什么的 下面我就要用一个新的符号
1.2 运算符重载
这里的运算符重载 和函数重载的重载不是一个意思对运算符的行为重新定义operator+运算符做函数名
刚刚上面写的代码就可以写成下面这样bool operator==(const Date& x, Date& y){return x._year == y._year&& x._month == y._month&& x._day == y._day;} bool operator<(const Date& x, Date& y){if (x._year < y._year){return true;}else if(x._year == y._year){if (x._month < y._month){return true;}else if(x._month == y._month){if (x._day < y._day){return true;}}}return false;}
就可以这样使用了 class Date{public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int GetYear(){return _year;}private:int _year;int _month;int _day;};
第二种方法就是将函数放到类里面 class Date{public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}bool operator==(const Date& x, Date& y){return x._year == y._year&& x._month == y._month&& x._day == y._day;}bool operator<(const Date& x, Date& y){if (x._year < y._year){return true;}else if (x._year == y._year){if (x._month < y._month){return true;}else if (x._month == y._month){if (x._day < y._day){return true;}}}return false;}private:int _year;int _month;int _day;};
然后我们又发现,函数的参数太多了,因为成员函数有一个隐含的this
指针 class Date{public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}bool operator==(Date& y){return _year == y._year&& _month == y._month&& _day == y._day;}bool operator<(Date& y){if (_year < y._year){return true;}else if (_year == y._year){if (_month < y._month){return true;}else if (_month == y._month){if (_day < y._day){return true;}}}return false;}private:int _year;int _month;int _day;};
我们使用就可以这样使用了 我们通过查看反汇编再来了解一下
首先看一下内置类型的比较,是通过指令集的支持
1.3 赋值运算符重载
我们接着写日期类,下面写一个赋值运算符void operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;}
Date&
,返回的就是*this
,不是this
,这是个指针 Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}
但是有可能有人这样写代码:d1 = d1
d1 = d1
这里我们就可以加上一个判断,两个相等的话直接返回即可 1.4 赋值
Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
那么我们不写这个赋值,编译器不会不会自动生成一个呢?—>会的!
因为这个是那6个默认成员函数之一
内置类型会值拷贝,自定义类型会调用它的拷贝
总结:
赋值运算符重载格式 参数类型:const T&,传递引用可以提高传参效率返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值检测是否自己给自己赋值返回*this :要复合连续赋值的含义赋值运算符只能重载成类的成员函数不能重载成全局函数 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right){if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;}
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
二、日期类的实现
前面实现了一些逻辑,接下来我们就实现剩下的所有代码在最后~~2.1 判断小于
在上面已经说明了,这里就直接写出来了bool 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){if (_day < d._day){return true;}}}return false;}
2.2 判断等于
直接判断相不相等bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}
2.3 判断小于等于
上面实现了小于,下面我们来实现一下小于等于既然上面都写了小于和等于了,我们这里就可以直接复用bool operator<=(const Date& d){return *this <= d || *this == d;}
2.4 判断大于
这里的大于不就是小于等于的取反bool operator>(const Date& d){return !(*this <= d);}
2.5 判断大于等于
大于等于就是小于取反bool operator>=(const Date& d){return !(*this < d);}
2.6 判断不等于
不等于就是等于的取反bool operator!=(const Date& d){return !(*this == d);}
2.7 日期加等天数
2.8 获取月份天数
在实现日期加天数的时候我们需要再先实现一个获取月份天数因为这里的获取月份天数需要被频繁调用,我们这里使用inline,可以不写【在类内声明同时定义的成员函数自动转化为内联函数】我们还将数组使用static修饰,避免了频繁开辟然后再判断闰年的时候我们把判断月份放到了前面,首先判断月份是否为2,为真继续判断下面的,否则每次都要执行那么一长串的判断闰年inline int GetMonthDay(int year,int month){assert(month < 13 && month > 0);// 放到静态区static int MonthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };// 先判断月份if (month == 2 && (((year % 100 == 0) && (year % 4 == 0)) || (year % 400 != 0)))return 29;return MonthDays[month];}
然后继续来实现首先加上天数,然后判断当前月的天数和加上的天数然后进行减掉天数,月份+1,如果月份等于了13,年就+1,月份赋值为1 Date& operator+=(int day){// 这里就直接修改了_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);++_month;if (_month == 13){++_year;_month = 1;}}return *this;}
2.9 日期加天数
上面是实现了+=,+也就很好实现了,这里只需要另外开一块空间,修改别的空间就不会影响我这里的值这里不可以用引用返回,tmp是一个临时对象,必须用传值返回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;}
上面那个+和+=的代码相似,我们也可以复用一下 Date operator+(int day){Date tmp(*this);tmp += day;return tmp;}
上面的代码是+复用+=,那么我们也可以用+=复用+
Date& operator+=(int day){*this = *this + day;return *this;}
那么这两种写法哪一种比较好呢?
2.9.1 日期减等天数
经过上面的思考,我们先实现减等Date& operator-=(int day){_day -= day;while (_day <= 0){--_month;if (_month == 0){--_year;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;}
2.9.2 日期减天数
Date operator-(int day){Date tmp = *this;tmp -= day;return tmp;}
三、前置++ && 后置++
前置++很简单Date& operator++(){*this += 1;return *this;}
后置++要返回++后的值【这里要注意,后置++的操作符是有一个**(int)区分的**】后置++相比前置++的效率是低一些的 Date operator++(int){Date tmp = *this;*this += 1;return tmp;}
3.1 日期减日期【返回天数】
int operator-(const Date& d){int flag = 1;Date max = *this;Date min = d;if (*this < d){int flag = -1;max = d;min = *this;}// 相差天数int n = 0;while (min != max){++min;++n;}return n * flag;}
3.2 流插入
对于内置类型可以流插入,那么内置类型也可以流插入
对于流插入【cout】,对于库里面全局的ostream类型的对象 -->点我
void operator<<(ostream& out){out << _year << "年" << _month << "月" << _day << "日" << endl;}
作为成员函数重载,this指针占据第一个参数,Date必须是在操作数 所以我们就必须让ostream
占据第一个位置
想要占据第一个参数,就不能写成成员函数,就要写成全局函数
当我们写成全局函数的时候,·不能访问类的私有了,这里也要后面的友元
void operator<<(ostream& out, const Date& d){out << d._year << "年" << d._month << "月" << d._day << "日" << endl;}
这里想要改成多个插入,我们返回值就要改一下 ostream& operator<<(ostream& out, const Date& d){out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;}
3.3 流提取
流提取是cin
istream& operator>>(istream& in, Date& d){cout << "请依次输入年月日:>";in >> d._year >> d._month >> d._day;return in;}
3.4 检查输入日期是否合法
bool CheckInvalid(){if (_year <= 0|| _month < 1|| _month > 12|| _day < 1|| _day > GetMonthDay(_year,_month)){return false;}else{return true;}}
这个时候把可以改检查的地方就检查一下 四、日期类的实现【源码】
#include <iostream>#include <assert.h>using namespace std;class Date{public:// 构造函数Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;if (!CheckInvalid()){cout << "构造日期非法" << endl;}}// 判断等于bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}// 判断小于bool 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){if (_day < d._day){return true;}}}return false;}// 判断小于等于bool operator<=(const Date& d){return *this <= d || *this == d;}// 判断大于bool operator>(const Date& d){return !(*this <= d);}// 判断大于等于bool operator>=(const Date& d){return !(*this < d);}// 判断不等于bool operator!=(const Date& d){return !(*this == d);}// 日期加等天数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 operator+(int day){Date tmp(*this);//Date tmp = *this;tmp += day;return tmp;}// 日期加天数Date operator+(const Date& d){Date tmp(*this);tmp._day += d._day;while (d._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;}// 日期-=天数Date& operator-=(int day){_day -= day;while (_day <= 0){--_month;if (_month == 0){--_year;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;}// 日期减天数Date operator-(int day){Date tmp = *this;tmp -= day;return tmp;}// 前置++Date& operator++(){*this += 1;return *this;}// 后置++Date operator++(int){Date tmp = *this;*this += 1;return tmp;}// 日期-日期int operator-(const Date& d){int flag = 1;Date max = *this;Date min = d;if (*this < d){int flag = -1;max = d;min = *this;}int n = 0;while (min != max){++min;++n;}return n * flag;}// 获取月份天数[可以不写inline,类里默认就是]inline int GetMonthDay(int year, int month){assert(month < 13 && month > 0);// 放到静态区static int MonthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };// 先判断月份if (month == 2 && (((year % 100 == 0) && (year % 4 == 0)) || (year % 400 != 0)))return 29;return MonthDays[month];}// 拷贝构造Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);bool CheckInvalid(){if (_year <= 0|| _month < 1|| _month > 12|| _day < 1|| _day > GetMonthDay(_year,_month)){return false;}else{return true;}}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;};ostream& operator<<(ostream& out, const Date& d){out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;}istream& operator>>(istream& in, Date& d){while (1){cout << "请依次输入年月日:>";in >> d._year >> d._month >> d._day;if (!d.CheckInvalid()){cout << "输入非法日期,请重新输入" << endl;}else{break;}}return in;}
五、const修饰
5.1 const成员函数
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。class Date{public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}void Print(){cout << "Print()const" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}private:int _year; // 年int _month; // 月int _day; // 日};
这里会出现一个权限放大的问题 那么在参数就需要改成const Date*
为了解决这个问题,我们就要在函数后面的位置加上一个const
修饰的是this指针指向的内容
那么全部函数都可以加上const吗?—>不可以!如果函数内部要被修改,那肯定是不能加的上面写的日期类的部分函数也可以加上~~
5.2 小结一下:
成员函数如果是一个对成员变量只进行读访问的函数,建议加上const,这样const对象和非const对象都可以访问
成员函数如果是一个对成员变量进行读写访问的函数,不可以加上const,否则不能修改成员变量
const对象可以调用非const成员函数吗? --> 不可以!【权限放大】非const对象可以调用const成员函数吗?–> 可以!【权限缩小】const成员函数内可以调用其它的非const成员函数吗?–> 不可以!【权限放大】非const成员函数内可以调用其它的const成员函数吗?–> 可以!【权限缩小】5.3 默认成员函数【取地址及const取地址操作符重载】
前面章节学习了4个默认成员函数了,剩下两个再看一下class Date{public:Date* operator&(){return this;}const Date* operator&()const{return this;}private:int _year; // 年int _month; // 月int _day; // 日};
比如说不想让别人拿到我的地址,普通对象返回空,const对象返回假地址就可以这样写: Date* operator&(){return nullptr;}const Date* operator&()const{int i = 0;return (const A*)&i;}
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容! 最后本文主要学习了运算符重载这个知识点,并且在学习的时候实现了一个日期类,望烙铁们学有所成~~