在C++中,除了可以对函数重载外,还可以对大多数运算符实施重载。运算符重载与函数重载相比,稍微复杂一点。运算符重载是通过创建运算符重载函数来实现的。运算符重载函数可以是在类外定义的普通函数,也可以是类的成员函数或友元函数。
1. 在类外定义的运算符重载函数
C++为运算符重载提供了一种方法,即在进行运算符重载时,必须定义一个运算符重载函数,其名字为operator,后随一个要重载的运算符。例如,要重载“+”号,应该写一个名字为operator的函数。其他的运算符重载函数也应该以同样的方式命名。
C++语言对运算符重载制定了一些规则:
(1)C++中绝大部分的运算符允许重载,不能重载的运算符只有以下几个:
- . 成员访问运算符
- .* 成员指针访问运算符
- :: 作用域运算符
- Sizeof 长度运算符
- ?: 条件运算符
(2)C++语言中只能对已有的C++运算符进行重载,不允许用户自己定义新的运算符。例如,虽然某些程序语言将“*”作为指数运算符,但是C++语言编程时不能重载“ “,因为” * “不是C++运算符。
(3)重载不能改变运算符的操作对象(即操作数)的个数。
(4)重载不能改变运算符原有的优先顺序。
(5)重载不能改变运算符原有的结合特性。
(7)运算符重载的参数至少应有一个是类对象(或类对象的引用)。也就是说,运算符重载函数的参数不能全部是C++标准型。(这项规定的目的是,防止用户修改用于标准类型数据的运算符性质)
(8)运算符重载函数可以是普通函数,也可以是类的成员函数,还可以是类的友元函数。
(9)一般而言,用于类对象的运算符必须重载,但是赋值运算符“ = ”例外,不必用户进行重载。通常情况下,赋值运算符“ = ”可用于同类对象之间的相互赋值。
2. 友元运算符重载函数
运算符重载是通过创建运算符重载函数来实现的,运算符重载函数定义了重载的运算符将要进行的操作。如果运算符重载函数是在类的外部定义的普通函数,这个运算符重载函数只能访问类中的公有数据成员,而不能访问类的私有成员。实际上,类中的数据成员常常是私有成员或保护成员,为此运算符重载函数一般采用如下两种形式定义:一是定义为它将要操作的类的成员函数(简称为成员运算符重载函数);二是定义为类的友元函数(简称为友元运算符重载函数)。
2.1 定义友元运算符重载函数的语法形式
(1)在类的内部,定义友元运算符重载函数的格式如下:
1 | friend 函数类型 operator运算符(形参表){ |
(2)友元运算符重载函数也可以在类中声明友元函数的原型,在类外定义。
在类中,声明友元运算符重载函数原型的格式如下:
1 | class X { |
在类外,定义友元运算符重载函数的格式如下:
1 | 函数类型 operator运算符(形参表){ |
注意:由于友元运算符重载函数不是该类的成员函数,所以在类外定义时不需要缀上类名。
2.2 双目运算符重载
双目运算符(或称二元运算符)有两个操作数,通常在运算符的左右两侧。当用友元函数重载双目运算符时,两个操作数都要传递给运算符重载函数。
一般而言,如果在类X中采用友元函数重载双目运算符@,而aa和bb是类X的两个对象,则以下两种函数调用方法是等价的:
aa@bb; //隐式调用
operator@(aa,bb); //显示调用
说明:
(1)有时,在函数返回的时候,可以直接用类的构造函数来生成一个临时对象,而不用对该对象进行命名。
1 | Complex operator+(Complex& a,Complex& b){ |
2.3 单目运算重载符
单目运算符只有一个操作数,如-a、&b、!c、++p等。重载单目运算符的方法与重载双目运算符的方法是类似的。用友元函数重载单目运算符时,需要一个显示的操作数。
1 |
|
注意:
使用友元函数重载单目运算符“++”时,采用对象引用参数传递操作数,如果采用对象传递操作数,结果是不正确的。是因为形参是对象时,是通过传值的方法传递参数的,函数体内对形参op的所有修改都无法传到函数体外。
一般而言,如果在类X中采用友元函数重载单目运算符@,而aa是类X的对象,则以下两种函数调用方法是等价的:
@aa; //隐式调用
operator@(aa); //显示调用
说明:
(1)运算符重载函数operator@可以返回任何类型,甚至可以是void类型,但通常返回类型与它所操作的类的类型相同,这样可以使重载运算符用在复杂的表达式中。
(2)有的运算符不能定义为友元运算符重载函数,如赋值运算符“=”、下标运算符“[ ]”、函数调用运算符“()”等。
3. 成员运算符重载函数
在C++中,可以把运算符重载函数定义成某个类的成员函数,称为成员运算符重载函数。
3.1 定义成员运算符重载函数的语法形式
(1)在类的内部,定义成员运算符重载函数的格式如下:
1 | 函数类型 operator 运算符(形参表){ |
(2)成员运算符重载函数也可以在类中声明成员函数的原型,在类外定义。
在类的内部,声明成员运算符重载函数原型的格式如下:
1 | class X{ |
在类外,定义成员函数运算符重载函数的格式如下:
1 | 函数类型 X::operator运算符(形参表){ |
注意:由于成员运算符重载函数是该类的成员函数,所以在类外定义时需要缀上类名。
3.2 双目运算符重载
对双目运算符而言,成员运算符重载函数的形参表中仅有一个参数,它作为运算符的右操作数。另一个操作数(左操作数)是隐含的,是该类的当前对象,它是通过this指针隐含地传递给函数的。
一般而言,如果在类X中采用成员函数重载双目运算符@,成员运算符函数operator@所需的一个操作数由对象aa通过this指针隐含地传递,它的另一个操作数bb在参数表中显示,则以下两种函数调用方法是等价的:
aa@bb; //隐式调用
aa.operator@(bb); //显示调用
3.3 单目运算符重载
对单目运算符而言,成员运算符重载函数的参数表中没有参数,此时当前对象作为运算符的一个操作数。
1 |
|
由于this指针是指向当前对象的指针,因此语句“return *this;”返回的是当前对象的值,即调用运算符重载函数operator++的对象ob的值。
不难看出,对类Coord重载了运算符“++”后,对类对象的加1操作变得非常方便,就像对整型数进行加1操作一样。
4. 成员运算符重载函数与友元运算符重载函数的比较
(1)对双目运算符而言,成员运算符重载函数参数表中含有一个参数,而友元运算符重载函数参数表中有两个参数;对单目运算符而言,成员运算符函数重载函数参数表中没有参数,而友元运算符重载函数参数表中有一个参数。
(2)双目运算符一般可以被重载为友元运算符重载函数或成员运算符重载函数,但有一种情况,必须使用友元函数。
1 | 例如,如果将一个复数与一个整数相加,可用成员运算符函数重载“+”运算符 |
(3)成员运算符函数和友元运算符函数都可以用习惯方式调用,也可以用它们专用的方式调用。
习惯调用形式 | 友元运算符重载函数调用形式 | 成员运算符重载函数调用形式 |
---|---|---|
a+b | operator+(a,b) | a.operator+(b) |
-a | operator-(a) | a.operator-( ) |
a++ | operator++(a,0) | a.operator++(0) |
(4)C++的大部分运算符既可以说明为成员运算符重载函数,又可以说明为友元运算符重载函数。究竟选择哪一种运算符函数好一些,没有定论,这主要取决于实际情况和程序员的习惯。
一般而言,对于双目运算符,将它重载为友元运算符重载函数比重载为成员运算符重载函数便于使用。对于单目运算符,则选择成员运算符函数较好。如果运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则运算符重载函数必须用友元函数,而不能用成员函数。
以下的经验可供参考:
- 对于单目运算符,建议选择成员函数;
- 对于运算符“=、( )、[ ]、->、”只能作为成员函数;
- 对于运算符“+=、-=、/=、*=、&=、!=、~=、%=、<<=、>>=”,建议重载为成员函数;
- 对于其他运算符,建议重载为友元函数。