为类设计操作符的时候,必须选择是将操作符设置为类成员还是普通非成员函数,在某些情况下,程序员没有选择,操作符必须是成员,在另一些情况下,有些经验原则可知道我们做出决定,下面是一些指导原则。有助于决定将操作符设置为类成员还是普通非成员函数。
(1)赋值(=)下标[]调用()和成员访问->等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。
(2)像赋值一样,复合赋值操作符通常应定义为类的成员。与赋值不同的是,不一定非得这样做,如果定义非成员复合赋值操作符,不会出现编译错误。
(3)对称的操作符,如算术操作符,相等操作符,关系操作符和位操作符,最好定义为普通非成员函数。
1,输出操作符的重载:
为了与IO标准库一致,操作符应接受ostream&作为第一个形参,对类类型const对像的引用作为第二形参,并返回ostream的引用。
重载输出操作符的一般的简单定义:
ostream & operator<<(ostream& ct,const classType& ct)//第一个为左操作数,第二个参数为右操作数。 { os<<ct.value1<<ct.value2; return os; }
第二个形参一般应是对要哦输出的类类型的引用。该形参是一个引用以避免复制。它可以是const,因为一般而言输出一个对象不应该改变该对象。
必须将它设置为非成员操作符(友元或外部函数),这个应该好理解,因为它的返回值不可能为该类类型的对象。
2,加法操作符的重载
一般而言,将算术和关系操作符定义为非成员函数,像下面的例子
Sales_item operator+(const sales_item& s1 ,const Sales_item& s2) { sales_item s3(s1); s3+=s2; return s3; }
注意:为了与内置擦作数保持一致,加法返回一个右值,而不是一个引用。
算术操作符通常产生一个新值,该值是两个操作数的计算结果,它不同于任一操作数且在一个局部变量中计算,返回对那个变量的引用是一个运行错误。
既定义了算术操作符(如+)又定义了相关复合操作符(如+=)的类。一般应使用复合赋值实现算术操作符。
相等操作符:
通常c++中的类使用相等操作符表述对象是等价的。即,他们通常比较每个数据成员,如果所有对应都相同,则认为两个对象相等,
inline bool operator==(const Sales_item &s1,const Sales_item &s2) { return s1.value1==s2.value1&&s1.value2==s2.value2&&s1.value3==s2.value3; }
inline bool operator!=(const Sales_item &s1,const Sales_item &s2) { return !(s1==s2); }
(1)如果类定义了==操作符,该操作符的含义是两个对象包含同样的数据。
(2)如果类具有一个操作能确定该类型的两个对象是否相等,通常将该函数定义为operator==而不是创造命名函数。用户习惯于用==来比较对象,而且这样做比记住新名字更容易。
(3)如果定义了==,它也应该定义!=,用户会期待如果可以用某个操作符,则另一个也存在。
(4)相等和不等操作符一般应该相互联系起来定义。让一个操作符完成比较对象的实际工作,而另一个操作符只是调用前者。
3,下标操作符
类定义下标操作符是,一般需要定义两个版本,一个为非const成员并返回引用,另一个为const成员并返回const引用。
#include <vector> class Foo { public: int &operator[](const size_t); const int &operator[](const size_t)const; private: vector<int>data; };下标操作符本身可能看起来像这样:
int& Foo::operator[](const size_t index) { return data[index]; } const int& Foo::operator[](const size_t index)const { return data[index]; }
4,成员访问操作符
箭头操作符必须定义为类成员函数。解引用操作符不要求定义为成员,但将它作为成员一般是正确的。
解引用操作符合箭头操作符常在实现智能指针的类中。
5,自增自减操作符,
他们常在诸如迭代器这样的类实现。
class checkedPtr { public: checkedPtr(int *b,int *e):beg(b),end(e),curr(b){}; checkedPtr& operator++(); checkedPtr& operator--(); checkedPtr operator++(int); checkedPtr operator--(int); private: int *beg; int *end; int *curr; };
checkedPtr& checkedPtr::operator++()//前缀式操作符 { if(curr==end) { throw out_of_range("increment past the end of checkedptr!"); } ++curr; return *this; } checkedPtr checkedPtr::operator++(int)//后缀式操作符 { checkedPtr ret(*this);//保存当前值 ++*this;//前移一个元素。调用的前者++操作符。 return ret;//返回保存值 }同时定义前缀式操作符和后缀式操作符存在一个问题:他们的形参数目和类型相同,普通重载不能区别所定义的是前缀式还是后缀式。
为了解决这一问题,后缀式操作 符接受一个int型参数。使用后缀式是,编译器提供0作为这个形参的实参,尽管我们的前缀式操作符函数可以使用这个额外的形参,但通常不应该这么做,那个形参不是后缀式的正常工作所需要的它的唯一目的是使他们加以区别。
例如
显式调用:
checkedPtr parr(ia,ia+size);//ia是指向整形的一个数组 parr.operator++(0);//调用后缀式 parr.operator++();//调用前缀式隐式调用:
checkedPtr parr(ia,ia+size); parr++; ++parr;一般而言,最好前缀式和后缀式都定义,只定义前缀式或只定义后缀式的类景会习惯于使用两种形式的用户感到奇怪。
6,调用操作符和函数对象
可以为类类型的对象重载函数调用操作符,一般为表述操作的类重载调用操作符,例如,可以定义名为absInt的结构,该结构封装将int类型的值转换为绝对值的操作。
struct absInt{ int opertor()(int val) { return val<0?-val:val; };这个类很简单,它定义了一个操作:函数调用操作符,该操作符有一个形参并返回形参的绝对值。
通过为类类型的对象提供一个实参表而使用调用操作符,所用的方式看起来像一个函数调用:
int i=-42; absInt ansObj; Unsigned int ui=absobj(i);显式调用的形式:
int i=42; absInt absObj; unisgned int ui=absObj.operator()(i);
尽管absobj是一个对象而不是函数,我们仍然可以“调用”该对象,效果是运行有absObj对象定义的重载函调用操作符。该操作符接受一个int值并返回它的绝对值。
注意:函数调用操作符必须声明为成员函数,一个类可以定义函数调用操作符的多个版本,有形参的数目或类型加以区别。
定义了调用操作符的类,其对象长陈伟函数对象,即他们是行为类似函数的对象。