一、基本概念
1、重载运算符是具有特殊名字的函数:由关键字operator和其他要定义的运算符号共同组成
2、重载运算符的参数数量与该运算符作用的运算对象一样多
- 当一个重载的运算符是成员函数时,this会绑定到左侧运算对象,成员运算符函数的显示参数数量比运算对象的数量少一个
3、对于一个运算符函数,它要么是类的成员,或者至少含有一个类类型的参数
- 当作用符作用于内置类型的运算对象时,无法改变该运算符的含义
例:
//非成员运算符函数的等价调用 data1 + data2; //普通的表达式 operator+(data1, data2); //等价的函数调用 //operator运算符是成员函数 data1 += data2; //基于调用的表达式 data1.operator += (data2); //等价的调用
4、通常情况下,不应该重载:逗号、取地址、逻辑与、逻辑或运算符
5、当设计一个类时,首先应该考虑的是这个类提供哪些操作;如果某些操作在逻辑上与运算符相关,则他们适合定义成重载的运算符
- 如果类定义了operator==;通常也应该定义operator!=
- 相等运算符合不相等运算符中的一个应该把工作委托给另一个
- 赋值运算符和复合赋值运算应该返回左侧运算符的一个引用
6、只有当操作的含义对于用于来说清晰明了是才运用运算符
7、定义成成员函数还是非成员函数的准则:
- 赋值(=)、下标([ ])、调用(())和成员访问箭头(->)运算符必须是成员
- 复合赋值运算符一般来说应该是成员
- 改变对象状态的运算符或者与给定类型密切相关的运算符,如递增、递减和解引用运算符,通常应该是成员
- 具有对称性的运算符可能转换任意一端的运算对象,例如算术、相等性、关系和为运算符等,通常应该是普通的非成员函数
二、输入和输出运算符
1、operator<<一般返回它的ostream形参
2、令输出运算符尽量减少格式化操作可以使用户有权控制输出的细节
3、如果希望为类自定义IO运算符,则必须将其定义成非成员函数,可以设置IO运算符声明为友元
- 因为如果是成员函数,则会默认第一个参数绑定在了左侧对象上
4、operator>>通常返回某个给定流的引用
- 输入运算符必须处理输入可能失败的情况,而输出运算符不许需要
- 当流含有错误类型的数据时读取会错误
- 当读取操作达到文件末尾或者遇到输入流的其他错误时也会失败
- 当读取操作发生错误时,输入运算符应该负责从错误中恢复
三、算术和关系运算符
1、如果同时定义了算符运算符(+)和相关的复合赋值运算符(+=),则通常情况下应该使用复合赋值来实现算术运算符
2、如果存在唯一一种逻辑可靠的<定义,则应该考虑为这个类定义<运算符。如果类同时包含==,则当且仅当<的定义和==产生的结果一致时才定义<运算符
- 因为有可能,一个类里面有很多种类的数据;不可以单纯的判断哪个大哪个小
四、赋值运算符
1、除了前面的赋值和移动赋值运算符,可以把一个类的对象赋值该类的另一个对象;还可以定义其他赋值运算符以使用别的类型作为右侧运算对象:如花括号的列表元素
2、和拷贝赋值和移动赋值一样,其他重载的赋值运算符也必须先释放当前内存空间,再创建一个新空间
3、赋值运算符必须定义成类的成员;复合赋值运算符通常也应该是类的成员;这两类运算符都应该返回左侧运算对象的引用
五、下标运算符
1、下标运算符通常以所访问元素的引用作为返回值
2、我们最好同时定义下标运算符的常量版本和非常量版本;一个返回普通引用,另一个是类的常量成员并且返回常量引用
六、递增和递减运算符
1、定义递增和递减运算符的类应该同时定义前置版本和后置版本。这些运算符都应该被定义成类的成员
2、前置运算符应该返回递增或者递减后对象的引用
3、后置版本接收一个额外的(不被使用)的int类型的形参
- 后置运算符应该返回对象的原值,返回的形式是一个值而非引用
StrBlobPtr StrBlobPtr::operator++(int) { StrBlobPtr ret = *this;//提前把*this即原值保存了下来 ++*this; //这里使用了前置版本++运算符 return ret; //注意这里返回的是ret }
- 后置运算符调用各自的前置版本来完成实际工作
4、如果我们想通过函数调用的方式调用后置版本,则必须为它的整型参数传递一个值,尽管这个值不被使用
StrBlobPtr p(a1); p.operator++(0);
七、成员访问运算符
1、箭头运算符必须是类的成员。解引用通常也是,但不是必须
2、operator*可以完成任何我们所指定的操作,比如打印值;而箭头运算符不是,只能获得成员
- 重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个类的对象
八、函数调用运算符
1、函数调用运算符必须是一个成员函数,一个类可以定义多个不同版本的调用运算符,相互之间应该在参数数量或类型上有所区别
2、函数对象常作为泛型算法的实参
3、标准库functional头文件中定义了一组算术运算符、关系运算符和逻辑运算符的类,每个类定义了一个执行命名操作的调用的运算符
- 标准库规定其函数对象对于指针同样适用
4、可调用对象与function
- 可调用对象:函数、函数指针、lambda表达式、bind创建的对象、以及重载了函数调用运算符的类
- 两个不同类型的可调用对象可能共享一个调用形式:调用形式指明了调用返回的类型以及传递给调用的实参类型
八、重载、类型转换与运算符
1、转换构造函数和类型转换运算符共同定义了类类型转换
2、类型转换运算符:operator type() const;
- 类型转换运算符可以面向任意类型进行定义,只要该类型能作为函数的返回类型
- 不允许转换成数组或函数类型,但允许转换成指针或引用类型
- 类型转换运算符既没有显式的返回类型,也没有形参,而且必须定义成类的成员函数,
- 类型转换运算符通常不应该改变待转换对象的内容,因此类型转换运算符一般定义成const成员
3、除了显式地向bool类型的转换外,应该尽量避免类型转换函数并尽可能限制那些“显然正确”的非显示构造函数
4、如果我们对同一个类既提供了转换目标是算术类型的类型转换,也提供了重载的运算符,则会遇到重载运算符与内置运算符的二义性问题