第12章C语言
- 格式:doc
- 大小:81.00 KB
- 文档页数:13
第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 <iostream.h>
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<<num3.numerator<<"/"<<num3.denominator<<endl;}
运行结果:
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 <iostream.h>
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<<num3.numerator<<"/"<<num3.denominator<<endl;}
运行结果:
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 <iostream.h>
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 <iostream.h>
#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<<real<<”+”<<”(”<<imag<<”)”<<”i”<<endl;
else cout<<real<<”+”<<imag<<”i”<<endl;}
//Ex12_2_1.cpp文件
#include <iostream.h>
#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 <iostream.h>
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 <iostream.h>
#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=”;
if(imag<0) cout<<real<<”+”<<”(”<<imag<<”)”<<”i”<<endl;
else cout<<real<<”+”<<imag<<”i”<<endl;}
//Ex12_2_2.cpp文件
#include <iostream.h>
#include “complex1.h”
void main()
{complex a(-1,8); //定义复数类的对象a
complex b(2,-11); //定义复数类的对象b
complex c; //定义复数类的对象c
cout<<”first output(a,b):”<<endl; 1
c=a+b;
c.show();
++a;
++b;
cout<<”second output(++a,++b):”<<endl;
c=a+b;
c.show();
a++;
b++;
cout<<”third output(a++,b++):”<<endl;
c=a+b;
c.show();}
运行结果:
first output(a,b):
a+b=1+(-3)i
second output(++a,++b):
a+b=3+(-1)i
third output(a++,b++):
a+b=5+1i
程序说明
♣本程序由头文件complex1.h、同名的源文件complex1.cpp、和源文件Ex12_2_2.cpp 这3个文件组合在一个项目中。
♣程序中把复数实部和虚部自增的前置++和后置++运算符重载为复数类的成员函数。
前置++和后置++单目运算符的重载的主要区别就是函数的形参:前置++单目运算符重载函数无形参,后置++单目运算符重载函数有一个整数形参。
这个整数形参只是用于区分前置和后置,因此参数表中无参数名,只是给出了类型名。
例12.2-3 赋值运算符“=”,“+=”,“-=”重载
#include <iostream.h>
class point
{public:
point(){}
point(int aa,int bb)
{a=aa;b=bb;}
void show()
{cout<<”(”<<a<<”,”<<b<<”)”<<endl;}
point operator=(point &);
point operator+=(point &);
void operator-=(point c)
{a-=c.a;
b-=c.b;}
private:
int a,b;
};
point point::operator=(point &c)
{a=c.a;b=c.b;
return *this;}
point point::operator+=(point &c)
{a+=c.a;b+=c.b;
return *this;}
void main()
{point s(4,5),r(6,8),t;
t=s;
cout<<”t=”;
t.show();
cout<<”s:”;
s.show();
cout<<”r:”;
r.show();
s+=r;
cout<<”s+=r:”;
s.show();
s-=r;
cout<<”s-=r:”;
s.show();}
运行结果:
t=(4,5)
s:(4,5)
r:(6,8)
s+=r:(10,13)
s-=r:(4,5)
程序说明
♣赋值运算符“=”是双目运算符,赋值运算符的重载函数的一般格式为
A operator=(A &) 或着A& operator=(A &c)
其中A为类;operator为关键字;“=”为重载运算符;重载憾事有一个参数为类A的引用;函数返回值是类A的引用。
在主函数中的表达式t=s;,其中s和t是类point的2个对象。
s为已初始化的对象,将s的值赋给对象t;“=”为被重载的运算符,编译器将表达式解释为t.operator=(s),其含义是调用重载的赋值运算符函数来实现上述操作。
♣使用成员函数方式重载双目运算符,参数是对象本身的数据,不需要用参数输入,而是通过隐含的this指针传入的,即指向该成员函数的对象的指针。
如果是单目运算符,操作数由对象的传入,也就不需要任何参数了。
♣本程序在类中,用point operator=(point &);声明“=”运算符重载
point operator+=(point &);声明“+=”运算符重载而“-=”运算符重载使用了无返回值运算符重载函数的形式,即void operator-=(point c) {……}。
由此可见C++编程的灵活性。
12.3 运算符重载为类的友元函数形式
运算符重载为类的友元函数的形式,就可以自由地访问此类的任何数据成员。
当运算
符重载为友元函数时,函数的参数个数与原操作数个数相同。
因为友元函数对某个对象的数据进行操作,必须通过该对象的名称进行,因此使用到的参数都要进行传递,操作数的个数与函数原型相同。
若重载为类的成员函数(前面介绍的),函数参数比函数原型少一个。
这就是两种形式的不同之处。
一般情况下,单目运算符重载为成员函数,而双目运算符重载为友元函数。
这样的操作被认为是一种比较好的选择。
运算符重载为类的友元函数的一般格式为
friend 类型operator 运算符(形参表)
{
函数体;
}
在形参表,形参从左到右的顺序即为运算符操作数的顺序。
例12.3-1 用运算符重载为类的友元函数的形式,重做例12.2-1的复数四则运算
“+、-、*、/”
//complex2.h文件
#include <iostream.h>
class complex //定义复数类
{public:
complex(float r=0.0,float i=0.0) //构造函数
{real=r;imag=i;}
friend complex operator+(complex &c1, complex &c2);//运算符“+”重载友元函数
friend complex operator-(complex &c1, complex &c2);//运算符“-”重载友元函数
friend complex operator*(complex &c1, complex &c2);//运算符“*”重载友元函数
friend complex operator/(complex &c1, complex &c2);//运算符“/”重载友元函数
void show(); //显示输出复数的成员函数
private:
float real,imag; //复数实部real,复数虚部imag };
//complex2.cpp文件
#include <iostream.h>
#include “complex2.h”
complex operator+(complex &c1, complex &c2) //重载运算符“+”函数实现
{return complex(c1.real+c2.real,c1.imag+c2.imag);}
complex operator-(complex &c1, complex &c2) //重载运算符“-”函数实现
{return complex(c1.real-c2.real,c1.imag-c2.imag);}
complex operator*(complex &c1, complex &c2) //重载运算符“-”函数实现
{return complex(c1.real*c2.real-c1.imag*c2.imag, c1.real*c2.imag+c1.imag*c2.real);}
complex operator/(complex &c1, complex &c2) //重载运算符“-”函数实现
{return complex((c1.real*c2.real+c1.imag*c2.imag)/
(c2.real*c2.real+c2.imag*c2.imag),
(c1.imag*c2.real-c1.imag*c2.imag)/
(c2.real*c2.real+c2.imag*c2.imag));}
void complex::show() //成员函数(显示输出)的实现
{if(imag<0) cout<<real<<”+”<<”(”<<imag<<”)”<<”i”<<endl;
else cout<<real<<”+”<<imag<<”i”<<endl;}
//Ex12_3_1.cpp文件
#include <iostream.h>
#include “complex2.h”
void main()
{complex a(6,8),b(2,4),c; //定义复数类的对象a、b、c
c=a+b;
cout<<”a+b=”;
c.show();
c=a-b;
cout<<”a-b=”;
c.show();
c=a*b;
cout<<”a*b=”;
c.show();
c=a/b;
cout<<”a/b=”;
c.show();}
运行结果:
a+b=8+12i
a-b=4+4i
a*b=-20+40i
a/b=2.2+(-0.4)i
程序说明
♣程序有3个文件构成:头文件complex2.h是类complex的定义;同名的源文件complex2.cpp,是类的成员函数及友元运算符重载函数的实现(定义);Ex12_3_1.cpp是主函数main()文件。
♣在头文件complex2.h,定义了4个友元函数形式的类的运算符重载,即
friend complex operator+(complex &c1, complex &c2)
friend complex operator-(complex &c1, complex &c2)
friend complex operator*(complex &c1, complex &c2)
friend complex operator/(complex &c1, complex &c2)
重载后的运算符用于对复数进行四则运算,运算符的2个操作数为友元函数形式的运算符重载函数,对双目运算符所具有的2个参数。
将运算符重载为友元函数,就必须把操作数通过形参方式传给运算符重载函数。
♣例12.3-1与例12.2-1中主函数没有变化,程序运行结果相同(指复数四则运算部分,在例12.3-1中复数求负部分未做)。
♣主函数main()中表达式a+b 中,运算符“+”是被重载后的复数加法运算。
编译器将该式解释为operator+(a,b),调用程序中下列函数求值,即
complex operator+(complex &c1, complex &c2)
12.4 插入运算符和提取运算符的重载
前面介绍过运算符的重载,定义了一个类就相当于定义了一个新的数据类型,如复数、集合、向量、矩阵在数学上已有规范的运算,运算符重载功能使得新类型的使用非常方便。
C++I/O流类库(第15章介绍)支持对用户定义新的数据类型的输入/输出。
本节介绍插入运算符“<<”和提取运算符“>>”的重载。
重载后,使编程十分便捷。
下面用例题说明通过友元函数的形式重载插入运算符“<<”和提取运算符“>>”的方法。
例12.4-1 使用重载“+”运算符进行复数运算,使用重载插入运算符“<<”和提取运算符“>>”进行复数的输入/输出
#include <iostream.h>
class complex
{public:
complex(double r=0.0,double i=0.0)
{real=r;imag=i;}
friend complex operator+(complex &c1,complex &c2); //说明”+”重载为类友元函数
friend ostream& operator<<(ostream &str,complex &c);//说明”<<”重载为类友元函数
friend istream& operator>>(istream &str,complex &c); //说明”>>”重载为类友元函数
private:
double real,imag; //复数实部与虚部
};
complex operator+(complex &c1,complex &c2) //”+”重载为类成员函数的实现
{return complex(c1.real+c2.real,c1.imag+c2.imag);}
ostream& operator<<(ostream& str, complex &c) //”<<”重载为类成员函数的实现
{str<<c.real<<”+”<<c.imag<<”i”;
return str;}
istream& operator>>(istream &str,complex &c) //”>>”重载为类成员函数的实现
{str>>c.real>>c.imag;
return str;}
void main()
{complex a1,a2,a3; //定义类complex的对象a1,a2,a3
cout<<”Input complex number(a1 and a2): ”<<endl;
cin>>a1>>a2;
a3=a1+a2;
cout<<a3<<endl;}
运行结果:
Input complex number(a1 and a2):
10 20 30 40
40+60i
程序说明
♣程序在类complex中,通过友元函数的形式重载了加法、插入运算符和提取运算符。
重载后可以对复数这种类型的数据进行“+”运算和输入/输出。
♣定义重载插入运算符“<<”时,使用类ostream的对象引用作为返回值,因为流类对象cout是类ostream的对象。
♣定义重载提取运算符“>>”时,使用类istream的对象引用作为返回值,因为流类对
象cin是类istream的对象。
♣将重载运算符说明为complex类的友元函数,其目的是为了访问类中的私有成员。
运算符重载的几点说明
♣大多数运算符都可以重载,诸如:
+ - * / % ^ & | ~ !
=< > += -= *= /= %= ^= &= |=
<< >> || ++ -- ->* . -> [ ] ( )
new new[ ] delete delete[ ]
不能重载的运算符有4个:
:: 作用域分辨符;
. 成员选择符;
.* 通过函数指针选择成员;
?: 三目运算符。
♣重载之后的运算符的优先级和结合性不发生变化。
♣重载不改变原运算符的操作对象个数。
♣如果重载运算符的定义比较简单,则把它们定义成内联函数更便捷。
使用运算符重载可能使可读性降低。
一般使用原则是:当使用原有的运算符有困难时,才选择使用重载运算符。
习题。