重载的运算符是具有特殊名字的函数:它们的名字由关键字operator和其后要定义的运算符共同组成。重载的运算符也包含返回类型、参数列表以及函数体。重载运算符函数的参数数量与该运算符作用的运算对象数量一样多。除了重载的函数调用运算符operator()之外,其他重载运算符不能含有默认实参。
如果一个运算符函数是成员函数,则它的第一个(左侧)运算对象绑定到隐式的this指针上,因此,成员运算符函数的(显式)参数数量比运算符的运算对象少一个。
我们不能为内置类型重定义运算符。我们只能重载已有的运算符,而无权发明新的运算符号。
直接调用一个重载的运算符函数
我们可以像调用普通函数一样直接调用运算符函数,先指定函数名字,然后传入数量正确、类型适当的实参:
//一个非成员运算函数的等价调用
data1+data2; //普通的表达式
operator+(data1,data2); //等价的函数调用
我们像调用其他成员函数一样显式地调用成员运算符函数。
data1+=data2; //基于“调用”的表达式
data1.operator+=(data2); //对成员运算符函数的等价调用
某些运算符不应该被重载
因为使用重载的运算符本质上是一次函数调用,所以关于运算对象求值顺序的规则无法应用到重载的运算符上。特别是逻辑与运算、逻辑或运算符和逗号运算符的运算对象求值顺序规则无法保留下来。
使用与内置类型一致的含义
1、如果类执行IO操作,则定义移位运算符使其与内置类型的IO保持一致。
2、如果类的某个操作是检查相等性,则定义operator==;如果类有了operator==,意味着它通常也应该有operator!=
3、如果类包含一个内在的单序比较操作,则定义operator<;如果类有了operator<,则它也应该含有其他关系操作。
4、重载运算符的返回类型通过情况下应该与其内置版本的返回类型兼容:逻辑运算符和关系运算符应该返回bool,算术运算符应该返回一个类类型的值,赋值运算符和复合赋值运算符则应该返回左侧运算对象的一个引用。
选择作为成员或者非成员
当我们定义重载的运算符时,必须首先决定是将其声明为类的成员函数还是声明为一个普通的非成员函数:
1、赋值(=)、下标([ ])、调用(())和成员访问箭头(->)运算符必须是成员。
2、复合赋值运算符一般来说应该是成员,但并非必须,这一点与赋值运算符略有不同。
3、改变对象状态的运算符或者与给定类型密切相关的运算符,如递增、递减和解引用运算符,通常应该是成员。
4、具有对称性的运算符可能转换任意一端的运算对象,例如算术、相等性、关系和位运算符等,因此它们通常应该是普通的非成员函数。
当我们把运算符定义成成员函数时,它的左侧运算对象必须是运算符所属类的一个对象。例如:
string s="world";
string t=s+"!"; //正确:我们能把一个const char*加到一个string对象中
string u="hi"+s; //如果+是string的成员,则产生错误