代码改变世界

C++重载操作符和转换

2012-01-04 17:56  马哈鱼  阅读(557)  评论(0编辑  收藏  举报

重载操作符的定义

重载操作符是具有特殊名称的函数:保留字 operator 后接需定义的操作符号。重载操作符具有返回类型和形参表。重载操作符的形参数目(包括成员函数的隐式 this 指针)与操作符的操作数数目相同,函数调用操作符可以接受任意数目的操作数。
重载操作符必须具有至少一个类类型或枚举类型的操作数。这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。重载操作符并不保证操作数的求值顺序,尤其是,不会保证内置逻辑 AND、逻辑 OR和逗号操作符的操作数求值。
操作符为非成员函数时,通常将它们设置为所操作的类的友元函数。

重载操作符的设计
不要重载具有内置含义的操作符。重载逗号、取地址、逻辑与、逻辑或等等操作符通常不是好做法。这些操作符具有有用的内置含义,如果我们定义了自己的版本,就不能再使用这些内置含义。

选择成员或非成员实现
赋值(=)、下标([])、调用(())和成员访问箭头(->)等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。
像赋值一样,复合赋值操作符通常应定义为类的成员,与赋值不同的是,不一定非得这样做,如果定义非成员复合赋值操作符,不会出现编译错误。
改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常就定义为类成员。
对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。

输出操作符的重载
为了与 IO 标准库一致,操作符应接受 ostream& 作为第一个形参,对类类型 const 对象的引用作为第二个形参,并返回对 ostream 形参的引用。函数原型类似于:
ostream&
operator <<(ostream& os, const ClassType &object);
IO操作符必须为非成员函数,通常将IO操作符设置为类的友元。

输入操作符的重载
输入操作符的第一个形参是一个引用,指向它要读的流,并且返回的也是对同一个流的引用。它的第二个形参是对要读入的对象的非 const 引用,该形参必须为非 const,因为输入操作符的目的是将数据读到这个对象中。输入操作符必须处理错误和文件结束的可能性。
函数原型类似于:
istream&
operator>>(istream& in, Sales_item& s);

算数操作符的重载
一般而言,将算数和关系操作符定义为非成员函数。算术操作符通常产生一个新值,该值是两个操作数的计算结果,它不同于任一操作数且在一个局部变量中计算,返回对那个变量的引用是一个运行时错误。根据复合赋值操作符(如 +=)来实现算术操作符(如 +),比其他方式更简单且更有效。如果我们调用 += 来实现 +,则可以不必创建和撤销一个临时量来保存 + 的结果。

相等操作符
如果类定义了 == 操作符,该操作符的含义是两个对象包含同样的数据。
如果类具有一个操作,能确定该类型的两个对象是否相等,通常将该函数定义为 operator== 而不是创造命名函数。用户将习惯于用 == 来比较对象,而且这样做比记住新名字更容易。
如果类定义了 operator==,它也应该定义 operator!=。
相等和不操作符一般应该相互联系起来定义,让一个操作符完成比较对象的实际工作,而另一个操作符只是调用前者。

赋值操作符
类赋值操作符接受类类型形参,通常,该形参是对类类型的 const 引用,但也可以是类类型或对类类型的非 const 引用。如果没有定义这个操作符,则编译器将合成它。类赋值操作符必须是类的成员,以便编译器可以知道是否需要合成一个。

下标操作符
类定义下标操作符时,一般需要定义两个版本:一个为非 const 成员并返回引用,另一个为 const 成员并返回 const 引用。

成员访问操作符
箭头操作符必须定义为类成员函数。解引用操作不要求定义为成员,但将它作为成员一般也是正确的。

调用操作符和函数对象
定义了调用操作符的类,其对象称为函数对象,即它们是行为类似于函数的对象。如下列所示;
struct absInt {
int operator() (int val) {
return val < 0 ? -val : val;
}
};

int i = -42;
absInt absObj;
unsigned int ui = absObj(i);
尽管 absObj 是一个对象而不是函数,我们仍然可以“调用”该对象,效果是运行由 absObj 对象定义的重载调用操作符。
函数调用操作符必须声明为成员函数。一个类可以定义函数调用操作符的多个版本,由形参的数目或类型加以区别。函数对象经常用作通用算法的实参。如下:
class GT_cls {
public:
GT_cls(size_t val = 0): bound(val) { }
bool operator()(const string &s)
{ return s.size() >= bound; }
private:
std::string::size_type bound;
};

cout << count_if(words.begin(), words.end(), GT_cls(6))
<< " words 6 characters or longer" << endl;
这个 count_if 调用传递一个 GT_cls 类型的临时对象。用整型值 6 来初始化那个临时对象,构造函数将这个值存储在 bound 成员中。现在,count_if 每次调用它的函数形参时,它都使用 GT_cls 的调用操作符,该调用操作符根据 bound 的值测试其 string 实参的长度。

标准库定义了算数函数对象,关系函数对象和逻辑函数对象,它们都是类模板。

转换操作符
转换操作符是一种特殊的类成员函数。它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字 operator 之后跟着转换的目标类型。一般而言,不允许转换为数组或函数类型,转换为指针类型(数据和函数指针)以及引用类型是可以的。转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。转换函数一般不应该改变被转换的对象。因此,转换操作符通常应定义为 const 成员。