第12章运算符重载
?12.1 什么是运算符重载
在第10章中曾介绍过函数重载,已经接触到重载(overloading)这个名词。所谓重载,就是重新赋予新的含义。函数重载就是对一个已有的函数赋予新的含义,使之实现新的功能。因此,同一个函数名就可以代表多个不同功能的函数,也就是一名多用。
运算符也可以重载。运算符重载的概念,其实并不陌生,只是此前没有做进一步的讲解。如“+”加法运算符,在C语言中已经用的很多了。在C++中,若对数值型数据进行“+、-、*、/”等操作,可写出如下程序
int a,b,c;
c=a+b; //对整型变量执行算术的加法运算。
float f1,f2,f3;
f3=f1+f2; //对实型变量执行算术的加法运算。
从中可以看出,运算符“+”既可以实现整型变量的加法,又可以实现实型变量的加法。这说明运算符“+”具有双重功能,它能够根据表达式中运算符两侧的数据类型自动调整该类型所需的数据操作方法。这样,就可以将同一运算符用于不同的数据类型上实现功能相同的操作。这就是C++中的运算符重载。
编译系统已经为基本数据类型定义(重载)了一些运算符。如为数值型数据,定义有“+、-、*、/”等运算符;为关系运算定义了“>、>=、<、<=、!=、==”等运算符;为系统输入输出定义了“>>、<<”操作符。但对于用户自定义的结构体及类只定义了赋值运算符“=”、成员访问运算符“.”和指针运算符“->”这三种运算符。换句话说,编译系统在语法中赋予了这些运算符或操作符相应的功能。编译源程序时,系统遇到什么操作符就执行什么操作。
若用户要对自定义类型数据进行算术、关系、或其他运算,就不能使用这些未加定义(重载)的运算符或操作符,如以下自定义类型数据的“+”运算。
struct fraction //定义表示分数(fraction)的结构体,为完成如(1/2)+(2/3)+(1/3)的加法{int numerator; //分子
int denominator; //分母
};
fraction num1,num2,num3;
num3= num1+ num2; //试图执行两个分数的加法运算,编译错误error C2676:
num1= num3- num2; //试图执行两个分数的减法运算,编译错误error C2676:
编译时提示error C2676:binary '+' : 'struct main::fraction' does not define this operator or a con- version to a type acceptable to the predefined operator错误。显然,编译系统不知道对num1, num2,num3变量应进行怎样的“+”和“-”操作?
若要实现结构体变量的加法或减法运算,可采用以下两种方法,一种是按照一般函数的定义规则分别定义一个用于完成加法功能的函数和一个用于完成减法功能的函数。另一种是按照运算符重载函数的定义规则分别对现有运算符“+”和“-”进行重载。
例12.1 -1用函数方法(方法一)来实现结构体变量的加法
#include
struct fraction //定义表示分数(fraction)的结构体,为完成如(1/2)+(2/3)+(1/3)的加法{int numerator; //分子
int denominator; //分母
};
fraction num1,num2,num3;
fraction Add(const fraction &num1,const fraction &num2) //定义函数
{fraction temp_num; //定义临时工作变量
int n,m,k;
n=num1.numerator*num2.denominator;
m=num2.numerator*num1.denominator;
temp_num.numerator=n+m; //计算分子
temp_num.denominator=num1.denominator*num2.denominator; //计算分母
n=temp_num.numerator;
m=temp_num.denominator;
/*将计算结果化简为最简分数*/
k=m%n;
while(k)
{m=n;
n=k;
k=m%n;
}
temp_num.numerator/=n;
temp_num.denominator/=n;
/*化简完毕*/
return temp_num;
}
void main()
{fraction num1,num2,num3;
cin>>num1.numerator>>num1.denominator; //输入第一个分数的分子和分母
cin>>num2.numerator>>num2.denominator; //输入第二个分数的分子和分母
num3=Add(num1,num2); //将两个分数相加
cout< 运行结果: 2 3↙ 4 5↙ 22/15 程序说明 ?运行时,从键盘输入2 3↙表示2/3,4 5↙表示4/5。 ?对Add()函数的参数,使用常引用的方法,其目的是为防止在函数中改变该参数的值,以保证被引用的实参不会发生变化。 运算符重载的定义格式 运算符重载分为单目运算符的重载和双目运算符的重载。它是由关键字operator后跟一个运算符来实现的。一般格式为 单目运算符的重载格式 返回值类型operator 单目运算符(一个用户类型的参数声明){} 双目运算符的重载格式 返回值类型operator 双目运算符(第一个参数声明,第二个参数声明){} 格式说明 ?运算符的重载格式有些象函数的形式。即可以把operator运算符看作是一个函数名。因此,运算符重载又称作运算符重载函数。operator和后面的运算符之间有无空格均可。 ?运算符重载必须带有参数,并且至少要有一个用户自定义类型的参数。 ?对于单目运算符的重载来说,其参数表中只有一个用户自定义类型的参数。 ?对于双目运算符的重载来说,其参数表中第一个参数为双目运算符左边的对象,第二个参数为双目运算符右边的对象。且二者中必须有一个是用户自定义类型。 ?对于单目运算符的后增1或后减1运算符,在重载时参数表中要增加一个整型参数,以便与前增1前减1区别,该整型参数是虚设的,可以只列出类型。 如fraction operator ++( fraction &num,int); 例12.1 -2重载运算符“+”(方法二)实现结构体变量的加法 #include struct fraction {int numerator; //分子 int denominator; //分母 }; fraction num1,num2,num3; fraction operator +( const fraction &num1,const fraction &num2)//重载运算符“+” {…//同前例略 …} void main() {fraction num1,num2,num3; cin>>num1.numerator>>num1.denominator; cin>>num2.numerator>>num2.denominator; num3=num1+num2; //可以使用重载的“+”运算符将两个分数相加 cout< 运行结果: 2 3↙ 4 5↙ 22/15 由此,我们实现了使用“+”运算符对自定义数据类型变量的加法运算。它同函数实现的方法是一样的,但所表达的语义不同。可以看出,使用重载的运算符后,比函数调用更简单,更能说明程序的意图。该结构的减法运算请读者自己设计完成。 ?注意 ? C++中的运算符除了成员运算符“.”、条件运算符“?:”和作用域运算符“::”以外,其它全部可以重载,而且只能重载已有的运算符。 ?重载之后运算符的优先级、操作对象个数和结合性都不会改变。 ?运算符重载是针对新类型数据的实际需要,对原有运算符所能处理的数据类型范围进行扩大,使之能够对新的数据类型执行功能相同的运算。一般来讲,重载的功能应当与原有功能相类似,同时至少要有一个操作对象是用户自定义类型。 运算符重载实际上就是函数重载。对于运算符将其视为一个函数,只不过形式有点特殊。如,程序中遇有11+12的表达时,C++编译器将此表达式当作一次函数调用,即int operator+(11,12) 该函数给出2个整型数的加法操作,并返回一个整数; 而对于程序中遇有11.1+12.2时,C++编译器也将此表达式当作一次函数调用,即 int operator+(11.1,12.2) 该函数给出2个实型数的加法操作,并返回一个实型数。 以上2个函数很相似,不同之处是:第1个函数原型为int operator+(int a,int b);第2个函数原型为int operator+(float a,float b)。如果将“operator+”理解为一个函数名,则与一般函数没有什么区别。 运算符重载一般采用2种形式,一种为成员函数形式,另一种为友元函数形式。 ?12.2 运算符重载为类的成员函数形式 将运算符重载为类的成员函数,一般格式为 类型operator 运算符(形参表) { 函数体; } 其中类型是指重载运算符的返回值类型;operator是定义运算符重载的关键字;运算符是指要重载的运算符名称。形参表:在表中列出了重载运算符所需要的参数和参数类型。表中参数个数与重载运算符操作数的个数有关,即运算符重载函数的参数比原来的操作数少一个(后置++、--除外)。具体地说,单目运算符参数表无参数,双目运算符参数表中只有一个参数。也就是说,调用该参数的对象为第1个操作数,参数表中的参数为第2个操作数。 例12.2-1 运算符重载:复数四则运算与求负 分析:复数c1=a+bi,c2=x+yi。其中a,b,x,y均为实数; c=c1+c2=(a+x)+(b+y)i c=c1-c2=(a-x)+(b-y)i c=c1*c2=(a+bi)*(x+yi)= (ax-by)+(bx+ay)i c=c1/c2=(a+bi)/(x+yi)= ((ax+by)/(x2+y2))+((bx-ay)/( x2+y2))i c= - c1=-a-bi c= - c2=-x-yi //complex.h文件 #include class complex //定义复数类 {public: complex(float r=0.0,float i=0.0) //构造函数 {real=r;imag=i;} complex operator+(complex &c); //运算符“+”重载成员函数 complex operator-(complex &c); //运算符“-”重载成员函数 complex operator*(complex &c); //运算符“*”重载成员函数 complex operator/(complex &c); //运算符“/”重载成员函数 complex operator-(void); //运算符“求负”重载成员函数 void show(); //显示输出复数 private: float real,imag; //复数实部real,复数虚部imag }; //complex.cpp文件 #include #include “complex.h” complex complex::operator+(complex &c) //重载运算符“+”函数实现{complex p; p.real=real+c.real; p.imag=imag+c.imag; return p;} complex complex::operator-(complex &c) //重载运算符“-”函数实现{complex p; p.real=real-c.real; p.imag=imag-c.imag; return p;} complex complex::operator*(complex &c) //重载运算符“*”函数实现{complex p; p.real=real*c.real-imag*c.imag; p.imag=real*c.imag+imag*c.real; return p;} complex complex::operator/(complex &c) //重载运算符“/”函数实现{complex p; p.real=(real*c.real+imag*c.imag)/(c.real*c.real+c.imag*c.imag); p.imag=(imag*c.real-real*c.imag)/( c.real*c.real+c.imag*c.imag); return p;} complex complex::operator-(void) //重载“求负”操作函数实现{complex p; p.real=-real; p.imag=-imag; return p;} void complex::show() {if(imag<0) cout< else cout< //Ex12_2_1.cpp文件 #include #include “complex.h” void main() {complex a(6,8); //定义复数类的对象a cout<<”a=”; a.show(); complex b(2,4); //定义复数类的对象b cout<<”b=”; b.show(); complex c1; //定义复数类的对象c1 c1=a+b; //使用重载运算符完成复数加法 cout<<”a+b=”; c1.show(); c1=a-b; //使用重载运算符完成复数减法 cout<<”a-b=”; c1.show(); c1=a*b; //使用重载运算符完成复数乘法 cout<<”a*b=”; c1.show(); c1=a/b; //使用重载运算符完成复数除法 cout<<”a/b=”; c1.show(); c1=-a; //使用重载运算符完成复数“求负” cout<<”-a=”; c1.show(); c1=-b; //使用重载运算符完成复数“求负” cout<<”-b=”; c1.show();} 运行结果: a=6+8i b=2+4i a+b=8+12i a*b=-20+40i a/b=2.2+(-0.4)i -a=-6+(-8)i -b=-2+(-4)i 程序说明 ?把复数的四则运算(+、-、*、/)和“求负”重载为复数类的成员函数。只在函数说明和实现时用了关键字operator。在运算过程中,成员函数形式的运算符重载函数与类的一般成员函数完全相似,可以直接通过运算符、操作数来实现函数的调用。运算符+、-、*、/和“求负”的功能没有变,对整型和实型等基本类型数据的运算仍遵循C++的基本语法规则,但是增加了对复数运算的功能。 ?程序中出现的为双目运算符,一般情况为: a运算符b。如 a+b,编译器将解释为 a.operator运算符(b),即 a.operator+(b)。其中a和b为类的对象。若运算符“+”被重载为复数运算的加法运算符,其中a为第1个操作数,b为第2个操作数,则它们都是complex的对象。 对于单目运算符,一般情况为:a运算符或运算符a。编译器将解释为 a.operator运算符() ?本程序有3个文件构成:头文件complex.h,扩展名为.h,类的成员函数形式的运算符重载在头文件中进行说明;与头文件同名的源文件complex.cpp,扩展名为.cpp,类的成 员函数形式的运算符重载函数在这个文件中定义(实现);文件Ex12_2_1.cpp,扩展名为.cpp,是主函数文件。将3个文件组合在一个项目中,这个程序才能运行,并得到结果。 例12.2-2 运算符重载:单目运算符++进行复数运算 //complex1.h文件 #include class complex //定义类complex {public: //公有成员函数,外部接口 complex(float a=0.0,float b=0.0) //构造函数 {real=a;imag=b;} void operator++(); //前置单目运算符重载 void operator++(int); //后置单目运算符重载 complex operator+(complex &c); //运算符“+”重载成员函数 void show(); //显示输出复数 private: //私有数据成员 float real,imag; //复数实部real,复数虚部imag }; //complex1.cpp文件 #include #include “complex1.h” void complex::operator++() //前置单目运算符重载函数实现 {real++;imag++;} void complex::operator++(int) //后置单目运算符重载函数实现 {real++;imag++;} complex complex::operator+(complex &c) //重载运算符“+”函数实现 {complex p; //定义复数类的对象 p.real=real+c.real; p.imag=imag+c.imag; return complex(p.real,p.imag);} void complex::show() {cout<<”a+b=”;