C++之运算符重载
可以将运算符看作是一种特殊的函数:操作数是函数的参数,运算结果是函数的返回值,
如果运算符被看作是函数,自然也可以像函数一样重载。
已见过的运算符重载现象:移位运算符>>和<<,用作流输入和输出运算符
定义重载运算符和定义普通函数类似:
函数名:由关键字operator 和其后要定义的运算符组成,例:operator=, operator<<, operator+
返回类型:运算结果的类型
参数表:提供参与运算的操作数
参数个数取决于运算符的操作数个数和运算符函数是成员函数还是非成员函数
函数体:进行运算,返回运算结果,即表达式的值
类的成员运算符与非成员运算符:
类的成员运算符函数:this 指向的对象被作为运算符的第一个操作数(左),因此一元运算符函数不需要提供参数,二元运算符提供一个参数作为右操作数。
非成员运算符函数:一元运算符要提供一个类类型的参数,二元运算符需要提供两个参数分别作为左、右操作数,其中至少一个参数必须是类类型的。
通常声明为类的友元,以便访问私有数据成员。
例:类X的二元运算符“+”(算术加)
//X的成员运算符函数operator+()类外定义
X X::operator+(const X& right){...}
//X的非成员运算符函数operator+()
X operator+(const X& left, const X& right){...}
运算符函数的调用:
运算符函数只有在类类型的对象参与运算时才起作用,当运算符作用于内置类型的运算对象时,不会改变该运算符原来的含义。
将运算符作用于类型正确的实参时,会引起重载运算符的调用。
当然调用方式不止一种:可以像对待普通函数一样直接调用运算符函数,不过较少用,下面是运算符重载后调用的几种方式:
X data1, data2;
data1 + data2; //表达式中的调用
operator+(data1, data2); //全局函数直接调用
data1.operator+(data2); //成员函数直接调用
运算符函数的参数和返回类型
对于类类型的参数,如果仅仅只是读参数的值,而不改变参数,应该作为const引用来传递
普通算术运算符、关系运算符、逻辑运算符都不会改变参数,所以以const引用作为参数传递方式。
当运算符函数是类的成员函数时,就将其定义为const成员函数
返回值的类型取决于运算符的具体含义:
如果使用运算符的结果是产生一个新值,就需要产生一个作为返回值的新对象,通过传值方式返回,通常由const限定。
如果函数返回的是操作数对象,则通常以引用方式返回,根据是否希望对返回的对象进行操作来决定是否返回const引用
例:
所有赋值运算符均改变左值:
为了使赋值结果能用于链式表达式,如a=b=c,应该返回一个改变了的左值的引用
一般赋值运算符的返回值是非const引用,以便能够对刚刚赋值的对象进行运算
逻辑运算符和关系运算符最好返回bool值,也可以返回int值或者由typedef定义的等价类型
定义运算符函数时选择成员还是非成员?
赋值(=)、下标([])、函数调用(())和成员函数访问箭头(->)运算符必须是成员函数
赋值运算符只能用成员函数重载,复合赋值运算符一般应该是成员,但并非必须
改变对象状态的运算符或者与给定类型密切相关的运算符,如自增、自减和解引用运算符,通常应该是成员
具有对称性的运算符可能转换两个操作数中的任何一个,如算术、关系和位运算符等,通常应该是非成员函数
重载移位运算符<<和>>用于对象的I/O 操作时,左操作数是标准库流对象,右操作数才是类类型的对象,只能用非成员函数
使用成员运算符的限制:
成员运算符左操作数必须是当前类的对象,左操作数不能进行自动类型转换,非成员运算符为两个操作数都提供了转换的可能性
非成员运算符和成员运算符之间的选择:
如果没有区别,使用成员运算符,因为这样强调了运算符和类的密切关系
如果左操作数是其他类的对象,或希望运算符的两个操作数都能进行类型转换,则使用非成员函数重载运算符
运算符重载的应用:之前提到的部分应用如赋值运算符重载等就不再描述了。
有些类的对象可以像数组一样操作,可以为这样的类提供下标运算符:
下标运算符operator[]必须是成员函数,接收一个参数,通常是整值类型,返回一个元素的引用,以便用作左值
为了适用于const 和非const 对象,可以定义两个版本:
一个是非const 成员函数并返回引用
另一个是const 成员函数并返回const 引用或值
函数调用运算符:
如果类重载了函数调用运算符operator(),就可以像使用函数一样使用该类的对象
因为类同时能存储状态,所以比普通函数更灵活。
函数调用运算符是唯一不限制操作数个数的运算符,函数调用运算符必须是成员函数。
一个类可以定义多个不同版本的调用运算符,但是必须在参数个数或类型上有所区别,使用函数调用运算符的方式是令对象作用于一个实参列表,形式类似于函数调用。
如果一个类定义了函数调用运算符,那么该类的对象称为函数对象,或者仿函数(functor)。
例:
//通过带状态的函数对象,设定不同税率的计算
class Tax{
double rate;
int base;
public:
Tax(double r, int b) : rate(r), base(b){}
double operator()(double money){
return (money - base) * rate;
}
};
int main(){
Tax high(0.4, 30000); //第一种税率计算
Tax middle(0.25, 20000); //第二种税率计算
cout << "tax over 3w: " << high(38900) << endl;
cout << "tax over 2w: " << middle(27500) << endl;
}
//带状态的函数对象,设定不同的输出流对象和分隔符
class PrintString{
ostream &os;
char sep;
public:
PrintString(ostream &o = cout, char c = ' ')
: os(o), sep(c){}
void operator()(const string& str){os << str << sep; }
};
int main(){
PrintString welcomeMsg;
welcomeMsg("Welcome!");
PrintString errMsg(cerr, '\n');
errMsg("Error!");
}