在非面向对象的过程化语言中,要求每个过程或函数必须具有唯一的调用名,否则会导致编译错误。
面向对象程序设计语言提供使用同一函数名的机制,通过参数个数的不同或类型的不同来选择使用相应的代码,这就是函数重载。本节重点讲解成员函数重载和运算符重载。
成员函数重载
和普通函数类似,在一个类中也可以有成员函数重载。成员函数的重载在规则上和普通函数无差别,这里不再赘述。
例1 成员函数重载示例
#include
using namespace std;
class Sample
{
private:
int i;
double d;
public:
void setdata(int n) {i=n;d=0;}//setdata成员函数重载
void setdata(int n,double x) {i=n,d=x;}//setdata成员函数重载
void disp()
{
cout<<"i="<运算符重载
C++有许多内置的数据类型,包括int,char, double等,每种类型都有许多运算符,如加、减、乘、除等。当用户定义了类的对象时,两个对象之间是不能进行这些操作的,比如Sample类的对象a+b,这样的语句如果没有重载+运算符就会出错。但C++允许用户把这些运算符添加到自己的类中以方便类对象之间的运算,就像内置类型的运算一样方便,比如对象a+b就很容易懂,当然也可以在类中定义一个对象间相加的函数,比如a.add (b)调用函数add()以实现两个对象a和b相加,但是这条语句不能像a+b那样更容易让人理解。
为了重载运算符,必须定义一个函数,并告诉编译器,遇到这个重载运算符就可以调用该函数,由这个函数来完成该运算符应该完成的操作。这种函数称为运算符重载函数,它通常是类的成员函数或者是友元函数。运算符的操作数通常也应该是类的对象。
运算符重载形式有两种,重载为类的成员函数和重载为类的友元函数。
(1)运算符重载为类的成员函数
运算符重载为类的成员函数的一般语法形式为:
函数类型 operator运算符(形参表)
{
函数体;
}
其中operator是定义运算符重载函数的关键字,它与其后的运算符一起构成函数名。
对于双目运算符B,如果要重载B为类的成员函数,使之能够实现表达式oprd1 Boprd2,其中oprd1为类A的对象,则应当把B重载为类A的成员函数,该函数只有一个形参,形参的类型是oprd2所属的类型。经过重载后,表达式oprdl B oprd2就相当于函数调用oprdl. operator B(oprd2)。
对于前置单目运算符U,如“-”(负号)等,如果要重载U为类的成员函数,用来实现表达式U oprd,其中oprd为类A的对象,则U应当重载为类A的成员函数,函数没有形参。经过重载之后,表达式U oprd相当于函数调用oprd. operator U()。
对于后置运算符“++”和“--”,如果要将它们重载为类的成员函数,用来实现表达式oprd++或oprd--,其中oprd为类A的对象,那么运算符就应当重载为类A的成员函数,这时函数要带有一个整型形参。重载之后,表达式oprd++和oprd--就相当于函数调用oprd.operator+ (0)和oprd. operator-- (0);
运算符重载就是赋予已有的运算符多重含义。通过重新定义运算符,使它能够用于特定类的对象执行特定的功能,这便增强了C++语言的扩充能力。
例2 双目运算符重载示例
#include
using namespace std;
class complex
{
public:
complex() {real=imag=0;}
complex(double r,double i) {real=r;imag=i;}
complex operator+(const complex &c);//+运算符重载
friend void print(const complex &c);
private:
double real,imag;
};
inline complex complex::operator+(const complex &c)
{
return complex(real+c.real,imag+c.imag);
}
void print(const complex &c)
{
cout<<c.real<<"+"<<c.imag<<"i";
}
void main()
{
complex c1(2.0,3.0), c2(4.0,-2.0), c3;
c3=c1+c2;
cout<<"c1+c2="; printc3 codeprep data-track='21'>例2中当执行c3=cl +c2时,相当于执行c3=cl. operator+(c2)语句,即当两个complex类对象做加法操作时相当于去调用运算符重载函数。该程序的最终结果为6+li。例3 单目运算符重载示例
#include
using namespace std;
class Time
{
public:
Time() {minute=0; sec=0;}
Time(int m, int s): minute(m),sec(s) { }
Time operator++();
void display() {cout<<minute<<":"<<sec<=60)
{
sec-=60;
++minute;
}
return *this;
}
int main()
{
Time time1(34,0);
for (int i=0;i<60;i++)
{
++time1;
time1.display();
}
return 0;
}
例3中对++运算符进行重载。"++”运算符是单目运算符,只有一个操作数,因此,此运算符重载函数只有一个参数,且例6-3中该运算符重载函数作为类的成员函数时,则省略此参数。该例模拟秒表,每次走一秒,满60秒进一分钟,此时秒又从0开始算。
运算符重载为类的友元函数
与运算符重载为成员函数时不同的是,重载的友元函数不属于任何类,运算符的操作数都需要通过函数的形参表传递。操作数在形参表中从左到右出现的顺序就是用运算符写表达式时操作数的顺序。
这里分双目运算符和单目运算符两种情况,讨论运算符重载为友元函数的具体方式。如果有双目运算符U,它的其中一个操作数是类A的对象a,那么运算符U就可以重载为类A的友元函数,此友元函数的两个参数中,一个是类A的对象,另一个是其他对象,也可以是类A的对象。
这样双目运算符重载为类的友元函数后,假设运算符的一个操作数是对象b,则表达式aUb就相当于调用函数operator U(a, b)。如果有前置单目运算符U,比如前置“--”, a为类A的对象,如果要实现U a这样的运算,就可以把U重载为类A的友元函数,此友元函数只有一个形参,为类A的对象,重载后表达式U a相当于调用函数operator U(a)。如果是后置单目运算符U,如后置“++", a还是类A的对象,那么要实现a U这样的运算,也可以把U重载为类A的友元函数,此时友元函数就需要有两个形参,一个是类A的对象,另一个是整型形参,此整型形参没有实际意义,只是为了区分前置运算符和后置运算符的重载。重载后表达式aU就相当于调用函数operator U(a, 0)。
例4 修改例3为运算符重载为类的友元函数
#include
using namespace std;
class Time
{
public:
Time() {minute=0; sec=0;}
Time(int m, int s): minute(m),sec(s) { }
Time operator++();
void display() {cout<<minute<<":"<<sec<=60)
{
a.sec-=60;
++a.minute;
}
return a;
}
int main()
{
Time time1(34,0);
for (int i=0;i<60;i++)
{
++time1;
time1.display();
}
return 0;
}
注意:该例中是单目运算符前置重载,而且重载为友元函数。因此operator++有一个参数,此参数为Time类对象。该程序结果和例3结果一样,均为显示由34:1到35:0一秒一秒的计数过程。
不管运算符重载是以上哪种形式,都需要遵循以下规则。
(1) C++中的运算符除了少数几个之外,全部可以重载,而且只能重载C++中已有的运算符。(不能重载的运算符只有五个,它们是:成员运算符“.”、指针运算符“*”、作用域运算符“::"、类型说明符"sizeof”、条件运算符“?:”。)
(2)重载之后运算符的优先级和结合性都不会改变。