前言
先梳理一下本篇的脉络,首先会讲解运算符重载的概念,这是本篇的基本概念。其次会讲解赋值运算符的重载,这是本篇的重点,最后是++运算符重载,只需明晰规则即可。此外,希望这篇文章能让大家有所收获,如有不足之处,还请指正,小编会虚心接受并改进质量。
运算符重载
概念
C++引入了运算符重载。它和函数重载的概念类似,可以让一个符号有不同的功能,而具体的功能是由自己实现的。
目的
是为了增强代码的可读性。C++中有类这个概念。“ + ” 这个符号可以实现两个整形或浮点型相加,因为这两个类型是语言自己定义的。但它能实现两个类的相加吗?显然不行,因为类这个类型是自己设计的,相加要实现怎样的效果只有自己知道。
写法
运算符重载也是函数的一种,只不过函数名比较特殊,其返回值和参数列表和普通函数一样。参数的数量和操作数的数量一致,参数的顺序和操作数的顺序一致
函数名字为:关键字 operator 后面接需要重载的运算符符号。
函数原型: 返回值类型 operator操作符(参数列表)
示例(判断两个日期类是否相等)
调用
分为显式调用(加关键字)和隐式调用(不加关键字),如下代码
operator==(d1, d2)//显式调用
==(d1, d2) //隐式调用
注意事项
运算符重载的注意事项
1. 不能通过连接其他符号来创建新的操作符:比如operator@ |
2. 重载操作符必须有一个类类型参数 |
3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义 |
4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this |
5. .* :: sizeof ?: . 注意以上5个运算符不能重载。 |
详解注意事项
1.想要重载一个符号,必须复用语言定义过的符号,不能凭空捏造。如下代码就会报错。
bool operator@(const Date& d1, const Date& d2)
2.假如要把“ + ”这个符号重载成整形和字符型的相加,这毫无意义并且会破坏语言原有的运算规则,此时编译器会强制报错。
3.如果要把加“ + ”这个符号重载两个类相加,那具体实现的逻辑就必须是加的逻辑,但这一点没有强制性,因为这不是语法的错误,编译器不会强行报错。需要自行规范。
4.这一点会在下面提到。
5. .* :: sizeof ?: . 这五个运算符强制不能重载,大家可能对于 .* 运算符比较陌生, .* 运算符并不是本篇章的重点,但为了大家能够记住这个运算符,小编写了一段代码并且配上了详细的注释让大家感受一下 .* 运算符的作用。提醒:对 .* 运算符不感兴趣的可以跳过,不会对后面内容的理解有任何影响
class Date //日期类 { public: Date(int year = 1, int month = 1, int day = 1) //构造函数 { _year = year; _month = month; _day = day; } void DatePrintf() //打印函数 { cout << _year << _month << _day << endl; } private: int _year; //年 int _month; //月 int _day; //日 }; typedef void(Date::* DP)(); //这里是给一个函数类型取了一个别名,别名是 DP ,类型是 void () 日期类中的打印函数也是这个类型 int main() { DP dp = &Date::DatePrintf; //把成员函数中打印函数的指针给 dp , 一般来说函数名就代表该函数的地址,但成员函数规定要加上取地址符号 Date d; //实例化一个类 , 对象名为d (d .* dp)(); //调用d对象的打印函数 return 0; }
上述代码中提到了构造函数,对构造函数不是很理解的可以看一下,上述代码运行的结果是打印三个一,如下图:
运算符重载成全局性的弊端
类的数据一般是私有的,运算符重载是函数的一种,而全局性的函数是不可以访问类的私有数据的。如下图:
难道我们要为了运算符重载要把类中的数据设为共有吗,那类的封装性如何保证呢?本篇给出的解决方案是把运算符重载函数定义为成员函数,意思是直接定义在类中。如下代码
class Date //日期类 { public: Date(int year = 1, int month = 1, int day = 1) //构造函数 { _year = year; _month = month; _day = day; } bool operator==(const Date& d2) //运算符重载函数,一个参数是this ,一个是 const Date& { return _year == d2._year && _month == d2._month && _day == d2._day; } private: int _year; //年 int _month; //月 int _day; //日 };
类中隐含的this指针
如果把运算符重载函数定义成员函数会少定义一个参数,但并没有真的少了一个参数,有一个隐含的this指针作为了该函数的第一个参数。如下图:
this是类类型的隐含参数,如果运算符重载函数定义再了类里面,这个参数就可以省去不写。
赋值运算符重载
下面的内容涉及到拷贝构造函数,对拷贝构造函数的理解比较模糊的可以参考一下小编写的详解拷贝构造一文
赋值运算符重载格式
参数类型:类类型的引用
返回值:可传值返回,可传引用返回,传引用返回比传值返回效率要高,后面会细讲。
代码示例
Date& operator=(const Date& d) //日期类的赋值运算符的重载的定义 { _year = d._year; _month = d._month; _day = d._day; }
在这里先定义一个日期类,下面讨论的知识点都会用到这个日期类,大家先看一下代码
class Date //日期类 { public: Date(int year = 1, int month = 1, int day = 1) //全缺省构造函数 { _year = year; _month = month; _day = day; } Date(const Date& d) //拷贝构造函数 { _year = d._year; _month = d._month; _day = d._day; } void operator=(const Date& d) //赋值运算符重载 { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; };
注意点
赋值运算符只能在类中重载。因为赋值运算符重载函数是类的默认成员函数,如果重载成全局函数,编译器会为类自动生成默认赋值运算符重载函数,此时就会和全局的冲突。
明晰赋值运算符重载函数的调用
大家能分清下面的调用吗
int main() { Date d; //实例化d Date d1(d); //示例化d1,并把d的值拷贝给d1 Date d2 = d; //示例化d2,并把d的值拷贝给d2 Date d3; //实例化d3 d3 = d; //把d的值赋值给d3 }
画图演示其调用
连续赋值
上述代码中的赋值重载函数随是否能实现连续赋值呢?答案是不能。因为该函数只能完成赋值工作,并不会再把类的数据返回。代码改进
Date operator=(const Date& d) //赋值运算符重载 { _year = d._year; _month = d._month; _day = d._day; return *this; //返回被赋值的形参的数据 }
传引用与传值返回
上述代码中传的是类的值,由于类中封装了许多数据,想要把类的数据传回去,需要调用该类的拷贝构造函数,我们可以打印一下拷贝构造函数来观察一下,如下图
调用拷贝构造函数拷贝数据的消耗是很大的,如果传引用返回能不能减少拷贝呢,如下图
结果很明确,传引用返回并没有调用拷贝构造函数,这说明传引用返回比传值返回效率更高,只要该类出了作用域不会销毁,都可以传引用返回
如果我要自己给自己赋值会怎么样呢,这是没必要的,可以再改进一下代码
Date& operator=(const Date& d) //赋值运算符重载 { if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; }
默认赋值运算符重载
用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。内置类型成员变量是直接赋值的,而自定义类型成员变量需要 调用对应类的赋值运算符重载完成赋值。
我将用如下代码为大家讲解默认运算符重载函数的浅拷贝的弊端
class Date //日期类 { public: Date() //构造函数 { _year = 1; _month = 1; _day = 1; _a = (int*)malloc(sizeof(int) * 7); //为a开辟空间 for (int i = 0; i < 7; i++) { _a[i] = 0; } } private: int _year; //年 int _month; //月 int _day; //日 int* _a; //指向一块空间 }; int main() { Date d; //实例化对象d Date d1; //实例化对象d1 d = d1; //把对象d1的数据赋值给对象d }
下面是代码调试示意图
下面是逻辑示意图
对象d的_a不再指向原有空间,使原有空间丢失,造成内存泄漏。 如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必 须要实现
前置++和后置++重载
Date& operator++() //日期的前置++ { _day += 1; return *this; } Date operator++(int) //日期类的后置++ { Date temp(*this); _day += 1; return temp; }
C++规定: 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载,后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
自动传递。
本篇的内容到此结束啦