重载操作符与转换
[0. 必须定义为类成员的操作符]
赋值 =;
下标 [];
调用 ();
成员访问箭头 ->;
[1. 输入和输出操作符]
支持 I/O 操作的类所提供的 I/O 操作接口,
一般应该与标准库 iostream 为内置类型定义的接口相同,
因此,许多类都需要重载输入和输出操作符。
为了与 IO 标准库一致,操作符应接受 ostream& 作为第一个形参,
对类类型 const 对象的引用作为第二个形参,并返回对 ostream 形参的引用。
重载输出操作符一般的简单定义如下:
// general skeleton of the overloaded output operator ostream& operator <<(ostream& os, const ClassType &object) { // any special logic to prepare object // actual output of members os << // ... // return ostream object return os; }
重载 "->" 必须返回指向类类型的指针,或者返回定义了自己的"->"的类类型对象。
[2. 定义自增/自减操作符]
C++ 语言不要求自增操作符或自减操作符一定作为类的成员,
但是,因为这些操作符改变操作对象的状态,所以更倾向于将它们作为成员。
在为类定义重载的自增操作符和自减操作符之前,
还必须考虑为我们自己的类定义自增操作符和自减操作符的前缀和后缀操作符。
前缀式操作符的声明,举例如下:
class CheckedPtr { public: CheckedPtr& operator++(); // prefix operators CheckedPtr& operator--(); // other members as before };
为了与内置类型一致,前缀式操作符应返回被增量或减量对象的引用。
这个自增操作符根据 end 检查 curr,从而确保用户不能将 curr 增量到超过数组的末端。
如果 curr 增量到超过 end,就抛出一个 out_of_range 异常;
否则,将 curr 加 1 并返回对象引用(自减操作符的行为与此类似):
// prefix: return reference to incremented/decremented object CheckedPtr& CheckedPtr::operator++() { if (curr == end) throw out_of_range ("increment past the end of CheckedPtr"); ++curr; // advance current state return *this; }
区别操作符的前缀和后缀形式
同时定义前缀式操作符和后缀式操作符存在一个问题:
它们的形参数目和类型相同,普通重载不能区别所定义的前缀式操作符还是后缀式操作符。
为了解决这一问题,后缀式操作符函数接受一个额外的(即,无用的)int 型形参。
使用后缀式操作符进,编译器提供 0 作为这个形参的实参。
尽管我们的前缀式操作符函数可以使用这个额外的形参,但通常不应该这样做。
那个形参不是后缀式操作符的正常工作所需要的,它的唯一目的是使后缀函数与前缀函数区别开来。
接上面的例子,后缀式操作符如下:
class CheckedPtr { public: // increment and decrement CheckedPtr operator++(int); // postfix operators CheckedPtr operator--(int); // other members as before };
为了与内置操作符一致,后缀式操作符应返回旧值(即,尚未自增或自减的值),
并且,应作为值返回,而不是返回引用。
后缀式操作符可以这样实现(后缀自减与此类似):
// postfix: increment/decrement object but return unchanged value CheckedPtr CheckedPtr::operator++(int) { // no check needed here, the call to prefix increment will do the check CheckedPtr ret(*this); // save current value ++*this; // advance one element, checking the increment return ret; // return saved state }
操作符的后缀式比前缀式复杂一点,必须记住对象在 +1 或 -1 之前的状态。
这些操作符定义了一个局部 CheckedPtr 对象,
将它初始化为 *this 的副本,即 ret 是这个对象当前状态的副本。
// Question: 讨论允许将空数组实参传给 CheckedPtr 构造函数的优缺点。
// Answer:
优点:构造函数的定义简单
缺点:导致所构造的对象有可能没有指向有效的数组,从而失去其使用价值
比较完善的做法是应该在构造函数中队参数检查。
// Question: 为什么没有定义自增和自减操作符的 const 版本?
// Answer:
因为对 const 对象不能使用自增或自减操作。
[3. 类类型转换和标准转换]
使用转换函数时,被转换的类型不必与所需要的类型完全匹配。
必要时可在类类型转换之后跟上标准转换以获得想要的类型。
注意:
1) 但是,类类型转换之后不能再跟另一个类类型转换。
如果需要多个类类型转换,则代码将出错。
2) 既为算术类型提供转换函数,又为同一类类型提供重载操作符,
可能会导致重载操作符和内置操作符之间的二义性。
通过定义内置操作符的重载版本,我们可以为自己的类型(即,类类型或枚举类型)
的对象定义同样丰富的表达式集合。
重载操作符必须具有至少一个类类型或枚举类型的操作符。
应用于内置类型时,与对应操作符具有同样数目的操作数、同样的结合性和优先级。
大多数重载操作符可以定义为类成员或普通非成员函数,
赋值操作符、下标操作符、调用操作符和箭头操作符必须为类成员。
操作符定义为成员时,它是普通成员函数。
具体而言,成员操作符有一个隐式 this 指针,该指针一定是第一个操作数,
即,一元操作符唯一的操作数,二元操作符的左操作数。
重载了 operator()(即,函数调用操作符)的类的对象,称为“函数对象”。
这种对象通常用于定义与标准算法结合使用的谓词函数。
转换操作符必须为所转换类的成员,无形参并且不定义返回值,
转换操作符返回操作符所具有类型的值。