首页 > 代码库 > C++ Primer 学习笔记_58_重载操作符与转换 --重载操作符的定义
C++ Primer 学习笔记_58_重载操作符与转换 --重载操作符的定义
重载操作符与转换
--重载操作符的定义
引言:
明智地使用操作符重载可以使类类型的使用像内置类型一样直观!
重载操作符的定义
重载操作符是具有特殊名称的函数:保留字operator后接定义的操作符符号。如:
Sales_item operator+(const Sales_item &,const Sales_item &);
除了函数调用操作符之外,重载操作符的形参数目(包括成员函数的隐式this指针)与操作符的操作数数目相同。函数调用操作符可以接受任意数目的操作数。
1、重载的操作符名
可重载的操作符 | |||||
---|---|---|---|---|---|
+ | - | * | / | % | ^ |
& | | | ~ | ! | , | = |
< | > | <= | >= | ++ | -- |
<< | >> | == | != | && | || |
+= | -= | /= | %= | ^= | &= |
|= | *= | <<= | >>= | [] | () |
-> | ->* | new | new[] | delete | delete[] |
不能重载的操作符 | |||
---|---|---|---|
:: | .* | . | ?: |
不能通过连接其他合法符号来创建任何新的操作符!
2、重载操作符必须具有一个类类型操作数
用于内置类型的操作符,其含义不能改变:
int operator+(int,int); //Error
重载操作符必须具有至少一个类类型或枚举类型的操作数。这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。
3、优先级和结合性是固定的
操作符的优先级、结合性或操作数数目不能改变。
有四个符号(+,-, * 和&)既可作一元操作符又可作二元操作符,这些操作符有的在其中一种情况下可以重载,有的两种都可以,定义的是哪个操作符由操作数数目控制。除了函数调用操作符operator()之外,重载操作符时使用默认实参是非法的。
4、不再具备短路求值特性
重载操作符并不保证操作数的求值顺序,尤其是:不会保证内置逻辑与(&&),逻辑或(||)和逗号操作符的操作数求值顺序。
在&&和||的重载版本中,两个操作数都要进行求值,而且对操作数的求职顺序不做规定。因此,重载&&、||和逗号操作符不是一种好的做法!
5、类成员与非成员
作为类成员的重载函数,其形参看起来比操作数数目少1。作为成员函数的操作符有一个隐含的this形参指针,限定为第一个操作数!
一般将算术和关系操作符(“对称”操作符)定义为非成员函数,而将赋值操作符定义为成员(下面会有详细介绍):
Sales_item &Sales_item::operator+=(const Sales_item &); Sales_item operator+(const Sales_item &,const Sales_item &);
当操作符为成员函数时,this指向左操作数,因此即使复合赋值是二元操作符,成员复合赋值操作符也只接受一个(显式的)形参。使用操作符时,一个指向左操作数的指针自动绑定到this,而右操作符限定为函数的唯一形参。
6、操作符重载和友元关系
操作符定义为非成员函数时,通常必须将它们设置为所操作类的友元,因为操作符通常需要访问类的私有部分!
class Sales_item { friend std::istream &operator>>(std::istream &,Sales_item &); friend std::ostream &operator<<(std::ostream &,const Sales_item &); public: Sales_item &operator+=(const Sales_item &); }; //不需要将加操作符设置为友元,它可以用public成员operator+=实现 Sales_item operator+(const Sales_item &,const Sales_item &);
7、使用重载操作符
使用重载操作符的方式,与内置类型操作数上的使用操作符的方式一样。
1)假定item1和 item2是 Sales_item对象,可以打印它们的和,就像打印两个int的和一样:
cout << item1 + item2 << endl;
2)也可以像调用普通函数一样调用重载操作符函数:
cout << operator+(item1,item2) << endl;
调用成员操作符函数与调用任意其他函数是一样的:指定运行函数的对象,然后使用点或箭头操作符获取希望调用的函数,同时传递所需数目和类型的实参。
重载操作符的设计
在设计类的使用,需要记住一些有用的经验规则,可以有助于确定应该提供哪些重载操作符(如果需要提供)。
1、不要重载具有内置含义的操作符
赋值操作符、取值操作符和逗号操作符对类类型操作数有默认含义。如果没有特定重载版本,编译器就定义一下这些操作符:
1)合成赋值操作符进行逐个成员赋值:使用成员自己的赋值操作符依次对每个成员进行赋值。
2)默认情况下,取地址操作符和逗号操作符在类类型对象上的执行,与在内置类型对象上的执行一样。取地址操作符返回对象的内存地址,逗号操作符从左至右计算每个表达式的值,并返回最右边操作数的值。
3)内置逻辑与(&&)与逻辑或(||)操作符使用短路求值。如果重新定义该操作符,将失去操作符的短路求值特性!
【最佳实践】
重载逗号、取地址、逻辑与、逻辑或等操作符通常不是好做法!这些操作符都具有拥有的内置含义,如果我们定义了自己的版本,就不能再使用这些内置含义!
有时我们需要定义自己的赋值运算。这样做时,它应表现得类似于合成操作符:赋值之后,左右操作数的值应是相同的,并且操作符应返回对左操作数的引用。重载的赋值运算应在赋值的内置含义基础上进行定制,而不是完全绕开。
2、大多数操作符对类对象没有意义
除非提供了重载定义,赋值、取地址和逗号操作符对于类类型操作数没有意义。
为类设计操作符,最好的方式是首先设计类的公共接口。定义了接口之后,就可以只考虑应将哪些操作符定义为重载操作符。那些逻辑上可以映射到某个操作符的操作可以考虑作为候选的重载操作符。如:
1)相等测试操作应使用operator=
2)一般通过重载移位操作符进行输入和输出
3)测试对象是否为空的操作可用逻辑非操作符operator!表示。
3、复合赋值操作符
如果一个类有算术操作符或位操作符,那么,提供相应的复合赋值操作符一般是个好主意!
4、相等和关系操作符
将要用作关联容器键类型的类应定义<操作符,因为关联容器默认使用键类型的 <操作符。即使该类型将只存储在顺序容器中,类通常也应该定义相等(==)和小于(<)操作符,理由是许多算法假定这个操作符存在。例如sort算法使用 <操作符,而find算法使用 ==操作符。
如果类定义了相等操作符,它也应该定义不等操作符!=。类用户会假设如果可以进行相等比较,则也可以进行不等比较。同样的规则也应用于其他关系操作符。如果类定义了<,则它可能应该定义全部的四个关系操作符(>,>=,<,<=)。
5、选择成员或非成员实现
1)赋值=、下标[]、调用()和成员访问箭头->等操作必须定义为成员,将这些操作符定义为非成员时会发生编译错误。
2)像赋值一样,复合赋值操作符通常应该定义为类的成员。但是如果不这么做,也不会发生编译错误。
3)改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常定义为类成员。
4)对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定位普通非成员函数。
【警告:审慎使用操作符重载 P434非常经典,值得细读!】
当内置操作符和类型上的操作存在逻辑对应关系时,操作符重载最有用!使用重载操作符而不是创造命名操作,可以令程序更自然、更直观,而滥用操作符重载使得我们的类难以理解。
【最佳实践】
当一个重载操作符的含义不明显时,给操作取一个名字更好!对于很少用的操作,使用命名函数通常也比用操作符更好,如果不是普通操作,没有必要为简洁而使用操作符!