C++【03】运算符的重载

一、运算符重载的定义

所谓重载,就是重新赋予新的含义。函数重载就是对一个已有的函数赋予新的含义,使之同一个函数名可以用来代表不同功能的函数。

二、运算符重载的方法

运算符重载的方法是定义一个重载运算符的函数,在需要执行被重载的运算符时,系统就自动调用该函数,以实现相应的运算。也就是说,运算符重载是通过定义函数实现的。运算符重载实质上是函数的重载。
重载运算符的函数一般格式如下:
函数类型 operator 运算符名称 (形参表列)
{ 对运算符的重载处理 }
例如,想将“+”用于Complex类(复数)的加法运算,函数的原型可以是这样的:
Complex operator+ (Complex& c1,Complex& c2);
在上面的格式中operator是关键字,是专门用于定义重载运算符的函数的,运算符名称就是C++提供给用户的预定义运算符。
注意:函数名称是由operator和运算符组成的, operator+就是函数名,意为“对运算符+重载”。
两个形参是Complex类对象的引用,即要求实参为Complex类对象。
在定义了重载运算符的函数后,可以说: 函数operator+重载了运算符+。在执行复数相加的表达式c1+c2时,系统就会调用operator+函数,把c1和c2作为实参,与形参进行虚实结合。
重载运算符“+”,使之能用于两个复数相加。

#include 
using namespace std;
class Complex
{public:
      Complex( ){real=0;imag=0;}
      Complex(double r,double i){real=r;imag=i;}
      Complex  operator+(Complex &c2);               //声明重载运算符的函数
      void display( );
 private:
      double real;
      double imag;
};
Complex Complex∷operator+(Complex &c2)          //定义重载运算符的函数
{ Complex c;
   c.real=real+c2.real;
   c.imag=imag+c2.imag;
return c;}

void Complex∷display( )
{ cout<<″(″<

运行结果:
c1=(3+4i)
c2=(5-10i)
c1+c2=(8,-6i)
对上面的运算符重载函数operator+还可以改写得更简练一些:
Complex Complex∷operator + (Complex &c2)
{return Complex(real+c2.real, imag+c2.imag);}
return语句中的Complex(real+c2.real, imag+c2.imag)是建立一个临时对象,它没有对象名,是一个无名对象。在建立临时对象的过程中调用构造函数。return语句将此临时对象作为函数的返回值。
需要说明的是: 运算符被重载后,其原有的功能仍然保留,没有丧失或改变。如3+5依然代表整数相加,是否调用运算符重载函数由编译系统判别运算符两侧的数据类型决定。
通过运算符重载,扩大了C++已有运算符的作用范围,使之能用于类对象。把运算符重载和类结合起来,可以在C++程序中定义出很有实用意义而使用方便的新的数据类型。
运算符重载使C++具有更强大的功能、更好的可扩充性和适应性,这是C++最吸引人的特点之一。

三、运算符重载的规则

(1) C++不允许用户自己定义新的运算符,只能对已有的C++运算符进行重载。
例如BASIC中可以用“”作为幂运算符,如用“35”表示35,但在C++中这样是不行的。
(2) C++允许重载的运算符
C++中绝大部分的运算符允许重载。具体规定见书中表4.1。
不能重载的运算符只有5个:
. (成员访问运算符)
.* (成员指针访问运算符)
∷ (域运算符)
sizeof (长度运算符)
?: (条件运算符)
前两个运算符不能重载是为了保证访问成员的功能不能被改变,域运算符和sizeof运算符的运算对象是类型而不是变量或一般表达式,不具重载的特征。
(3) 重载不能改变运算符运算对象(即操作数)的个数。如关系运算符“>”和“<”等是双目运算符,重载后仍为双目运算符,需要两个参数。运算符“+”,“-”,“”,“&”等既可以作为单目运算符,也可以作为双目运算符,可以分别将它们重载为单目或双目运算符。
(4) 重载不能改变运算符的优先级别。例如“
”和“/”优先于“+”和“-”,不论怎样进行重载,各运算符之间的优先级别不会改变。
(5) 重载不能改变运算符的结合性。如赋值运算符“=”是右结合性(自右至左),重载后仍为右结合性。
(6) 重载运算符的函数不能有默认的参数,否则就改变了运算符参数的个数,与前面第(3)点矛盾。
(7) 重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有一个是类对象(或类对象的引用)。也就是说,参数不能全部是C++的标准类型,以防止用户修改用于标准类型数据的运算符的性质。
int operator +(int a,int b) //错误
{return (a-b);}
两个参数一个是类对象,一个是C++标准类型的数据是可以的,如:
Complex operator +(int a, Complex &c)
{return Complex(a+c.real,c.imag);}
//可以,整数和复数相加
(8) 用于类对象的运算符一般必须重载,但有两个例外,运算符“=”和“&”不必用户重载。
① 赋值运算符(=)可以用于每一个类对象,可以利用它在同类对象之间相互赋值。但如遇系统提供的默认的对象赋值运算符不能满足程序的要求,例如,数据成员中包含指向动态分配内存的指针成员时,在复制此成员时可能出现危险,在这种情况下,就需要自己重载赋值运算符。
② 地址运算符&也不必重载,它能返回类对象在内存中的起始地址。
(9) 应当使重载运算符的功能类似于该运算符作用于标准类型数据时所实现的功能。
(10) 运算符重载函数可以是类的成员函数(如例4.2),也可以是类的友元函数,还可以是既非类的成员函数也不是友元函数的普通函数。

四、运算符重载函数作为类成员函数和友元函数

想将一个复数和一个整数相加,如c1+i,可以将运算符重载函数作为成员函数,如下面的形式:
Complex Complex∷operator+(int &i) //运算符重载函数作为Complex类的成员函数
{return Complex(real+i,imag);}
注意在表达式中重载的运算符“+”左侧应为Complex类的对象,如
c3=c2+i;
不能写成
c3=i+c2; //运算符“+”的左侧不是类对象,编译出错
如果出于某种考虑,要求在使用重载运算符时运算符左侧的操作数是整型量(如表达式i+c2,运算符左侧的操作数i是整数),这时是无法利用前面定义的重载运算符的,因为无法调用i.operator+函数。
可想而知,如果运算符左侧的操作数属于C++标准类型(如int)或是一个其他类的对象,则运算符重载函数不能作为成员函数,只能作为非成员函数。如果函数需要访问类的私有成员,则必须声明为友元函数。可以在Complex类中声明:
friend Complex operator+(int &i,Complex &c); //第一个参数可以不是类对象
在类外定义友元函数:
Complex operator+(int &i, Complex &c) //运算符重载函数不是成员函数
{return Complex(i+c.real,c.imag);}
双目运算符重载为友元函数时,在函数的形参表列中必须有两个参数,不能省略,形参的顺序任意,不要求第一个参数必须为类对象。但在使用运算符的表达式中,要求运算符左侧的操作数与函数第一个参数对应,运算符右侧的操作数与函数的第二个参数对应。如
c3=i+c2; //正确,类型匹配
c3=c2+i; //错误,类型不匹配
请注意,数学上的交换律在此不适用。如果希望适用交换律,则应再重载一次运算符“+”。如
Complex operator+(Complex &c, int &i) //此时第一个参数为类对象
{return Complex(i+c.real,c.imag);}
这样,使用表达式i+c2和c2+i都合法,编译系统会根据表达式的形式选择调用与之匹配的运算符重载函数。可以将以上两个运算符重载函数都作为友元函数,也可以将一个运算符重载函数(运算符左侧为对象名的) 作为成员函数,另一个(运算符左侧不是对象名的)作为友元函数。但不可能将两个都作为成员函数,原因是显然的。
C++规定,有的运算符(如赋值运算符、下标运算符、函数调用运算符)必须定义为类的成员函数,有的运算符则不能定义为类的成员函数(如流插入“<<”和流提取运算符“>>”、类型转换运算符)。
由于友元的使用会破坏类的封装,因此从原则上说,要尽量将运算符函数作为成员函数。但考虑到各方面的因素,一般将单目运算符重载为成员函数,将双目运算符重载为友元函数。
说明: 有的C++编译系统(如Visual C++ 6.0)没有完全实现C++标准,它所提供不带后缀.h的头文件不支持把运算符重载为友元函数。上面例4.3程序在GCC中能正常运行,而在Visual C++ 6.0中会编译出错。但是Visual C++所提供的老形式的带后缀.h的头文件可以支持此项功能,因此可以将程序头两行修改如下,即可顺利运行:
#include
以后如遇到类似情况,亦可照此办理。

五、双目运算符

双目运算符(或称二元运算符)是C++中最常用的运算符。双目运算符有两个操作数,通常在运算符的左右两侧,如3+5,a=b,i<10等。在重载双目运算符时,不言而喻在函数中应该有两个参数。下面再举一个例子说明重载双目运算符的应用。

六、重载单目运算符

单目运算符只有一个操作数,如!a,-b,&c,*p,还有最常用的++i和–i等。重载单目运算符的方法与重载双目运算符的方法是类似的。但由于单目运算符只有一个操作数,因此运算符重载函数只有一个参数,如果运算符重载函数作为成员函数,则还可省略此参数。
下面以自增运算符“++”为例,介绍单目运算符的重载。

七、重载流插入运算符和流提取运算符

C++的流插入运算符“<<”和流提取运算符“>>”是C++在类库中提供的,所有C++编译系统都在类库中提供输入流类istream和输出流类ostream。
cin和cout分别是istream类和ostream类的对象。在类库提供的头文件中已经对“<<”和“>>”进行了重载,使之作为流插入运算符和流提取运算符,能用来输出和输入C++标准类型的数据。因此,在本书前面几章中,凡是用“cout<<”和“cin>>”对标准类型数据进行输入输出的,都要用#include 把头文件包含到本程序文件中。
用户自己定义的类型的数据,是不能直接用“<<”和“>>”来输出和输入的。如果想用它们输出和输入自己声明的类型的数据,必须对它们重载。
对“<<”和“>>”重载的函数形式如下:
istream & operator >> (istream &,自定义类 &);
ostream & operator << (ostream &,自定义类 &);
即重载运算符“>>”的函数的第一个参数和函数的类型都必须是istream&类型,第二个参数是要进行输入操作的类。重载“<<”的函数的第一个参数和函数的类型都必须是ostream&类型,第二个参数是要进行输出操作的类。因此,只能将重载“>>”和“<<”的函数作为友元函数或普通的函数,而不能将它们定义为成员函数。

八、不同类型数据间的转换

1.标准类型数据间的转换

在C++中,某些不同类型数据之间可以自动转换,例如
int i = 6;
i = 7.5 + i;
编译系统对 7.5是作为double型数处理的,在求解表达式时,先将6转换成double型,然后与7.5相加,得到和为13.5,在向整型变量i赋值时,将13.5转换为整数13,然后赋给i。这种转换是由C++编译系统自动完成的,用户不需干预。这种转换称为隐式类型转换。
C++还提供显式类型转换,程序人员在程序中指定将一种指定的数据转换成另一指定的类型,其形式为
类型名(数据)

int(89.5)
其作用是将89.5转换为整型数89。
对于用户自己声明的类型,编译系统并不知道怎样进行转换。解决这个问题的关键是让编译系统知道怎样去进行这些转换,需要定义专门的函数来处理。

2.转换构造函数

转换构造函数(conversion constructor function) 的作用是将一个其他类型的数据转换成一个类的对象。
先回顾一下以前学习过的几种构造函数:
默认构造函数。以Complex类为例,函数原型的形式为
Complex( ); //没有参数
用于初始化的构造函数。函数原型的形式为
Complex(double r,double i); //形参表列中一般有两个以上参数
用于复制对象的复制构造函数。函数原型的形式为
Complex (Complex &c); //形参是本类对象的引用
现在又要介绍一种新的构造函数——转换构造函数。
转换构造函数只有一个形参,如
Complex(double r) {real=r;imag=0;}
其作用是将double型的参数r转换成Complex类的对象,将r作为复数的实部,虚部为0。用户可以根据需要定义转换构造函数,在函数体中告诉编译系统怎样去进行转换。
在类体中,可以有转换构造函数,也可以没有转换构造函数,视需要而定。以上几种构造函数可以同时出现在同一个类中,它们是构造函数的重载。编译系统会根据建立对象时给出的实参的个数与类型选择形参与之匹配的构造函数。
使用转换构造函数将一个指定的数据转换为类对象的方法如下:
(1) 先声明一个类。
(2) 在这个类中定义一个只有一个参数的构造函数,参数的类型是需要转换的类型,在函数体中指定转换的方法。
(3) 在该类的作用域内可以用以下形式进行类型转换:
类名(指定类型的数据)
就可以将指定类型的数据转换为此类的对象。
不仅可以将一个标准类型数据转换成类对象,也可以将另一个类的对象转换成转换构造函数所在的类对象。如可以将一个学生类对象转换为教师类对象,可以在Teacher类中写出下面的转换构造函数:
Teacher(Student& s)
{num=s.num;strcpy(name,s.name);sex=s.sex;}
但应注意: 对象s中的num,name,sex必须是公用成员,否则不能被类外引用。

3.类型转换函数

用转换构造函数可以将一个指定类型的数据转换为类的对象。但是不能反过来将一个类的对象转换为一个其他类型的数据(例如将一个Complex类对象转换成double类型数据)。
C++提供类型转换函数(type conversion function)来解决这个问题。类型转换函数的作用是将一个类的对象转换成另一类型的数据。如果已声明了一个Complex类,可以在Complex类中这样定义类型转换函数:
operator double( )
{return real;}
类型转换函数的一般形式为
operator 类型名( )
{实现转换的语句}
在函数名前面不能指定函数类型,函数没有参数。其返回值的类型是由函数名中指定的类型名来确定的。类型转换函数只能作为成员函数,因为转换的主体是本类的对象。不能作为友元函数或普通函数。
从函数形式可以看到,它与运算符重载函数相似,都是用关键字operator开头,只是被重载的是类型名。double类型经过重载后,除了原有的含义外,还获得新的含义(将一个Complex类对象转换为double类型数据,并指定了转换方法)。这样,编译系统不仅能识别原有的double型数据,而且还会把Complex类对象作为double型数据处理。
那么程序中的Complex类对具有双重身份,既是Complex类对象,又可作为double类型数据。Complex类对象只有在需要时才进行转换,要根据表达式的上下文来决定。
转换构造函数和类型转换运算符有一个共同的功能: 当需要的时候,编译系统会自动调用这些函数,建立一个无名的临时对象(或临时变量)。
一般情况下将双目运算符函数重载为友元函数。单目运算符则多重载为成员函数。

你可能感兴趣的:(C++)