运算符重载,运算符重载函数作为类的友元函数,重载运算符的规则,重载双目运算符,重载单目运算符,重载流插入和流提取运算符。
author: ZJ 08-1-1
1.什么是运算符重载
运算符重载的方法是定义一个重载运算符的函数,在需要执行被重载的运算符时,系统就自动调用该函数,以实现相应的运算。运算符重载实质上是函数的重载。
重载运算符的函数一般格式如下:
函数类型 operator 运算符名称(形参列表){对运算符的重载处理}
函数名是由operator和运算符组成,如operator+意思是“对运算符+重载”。
程序一
class Complex{
public:
Complex(double r=0,double i=0){
real=r;
imag=i;
}
Complex complex_add(Complex&);
Complex operator+(Complex&);//运算符重载
void display(string);
private:
double real,imag;
};
Complex Complex::complex_add(Complex &c2){//定义加法操作
return Complex(real+c2.real,imag+c2.imag);
}
Complex Complex::operator+(Complex &c2){//定义运算符重载
return Complex(real+c2.real,imag+c2.imag);
}
void Complex::display(string str){
cout<<str<<"=("<<real<<","<<imag<<"i)"<<endl;
}
int main(){
Complex c1(3,4),c2(5,-10),c3,c4;
c3=c1.complex_add(c2);//使用成员函数
c4=c1+c2;//使用重载了的运算符+
c1.display("c1");
c2.display("c2");
c3.display("c3/c1+c2");
c4.display("c4/c1+c2");
system("PAUSE");
return EXIT_SUCCESS;
}
|
这里有两种方式实现复数的加法,一种是使用成员函数complex_add(Complex&);另一种是使用运算符重载operator+(Complex&)。两种方法的效果是一样的。
2.运算符重载函数作为类的友元函数
上面的例子将运算符重载函数作为类的成员函数,下面的例子将运算符重载函数作为类的友元函数。声明为友元函数,则可以访问类的private成员。
程序二
class Complex{
public:
Complex(double r=0,double i=0){
real=r;
imag=i;
}
friend Complex operator+(Complex&,Complex&);//声明友元函数
void display(string);
private:
double real,imag;
};
Complex operator+(Complex &c1,Complex &c2){//运算符重载函数
return Complex(c1.real+c2.real,c1.imag+c2.imag);
}
void Complex::display(string str){
cout<<str<<"=("<<real<<","<<imag<<"i)"<<endl;
}
int main(){
Complex c1(3,4),c2(5,-10),c3;
c3=c1+c2;
c1.display("c1");
c2.display("c2");
c3.display("c3/c1+c2");
system("PAUSE");
return EXIT_SUCCESS;
}
|
运算符重载函数作为类的成员函数和作为类的友元函数的区别是什么?
如果将运算符重载函数作为成员函数,它可以通过this指针自由地访问本类的数据成员,因此可以少写一个参数。但必须要求运算表达式第一个参数(即运算符左侧的操作数)是一个类对象。而且与运算符函数的返回的类型相同。因为必须通过类的对象去调用该类的成员函数,而且只有运算符重载函数返回值与该对象同类型,运算结果才有意义。
程序一:
Complex Complex::operator+(Complex &c2){…}
int main(){
Complex c1(3,4),c2(5,-10),c3,c4;
c4=c1+c2;//使用重载了的运算符+
|
如果c1不是Complex类,它就无法通过隐式this指针访问Complex类的成员;如果函数返回值不是Complex类复数,显然这种运算是没有实际意义的。
如果将一个复数和一个整数相加,可以将运算符重载作为成员函数,注意在表达式中重载的运算符“+”左侧应为Complex类的对象。
Complex operator+(int);
Complex Complex::operator+(int i){
return Complex(real+i,imag);
}
c4=c1+1;//ok
c4=1+c1; error:no match for 'operator+' in '1 + c1'
|
如果使用友元类,第一个参数可以不是类对象。
friend Complex operator+(int,Complex&);
Complex operator+(int i,Complex &c){
return Complex(c.real+i,c.imag);
}
c4=1+c1;//ok
|
C++规定,有的运算符(如赋值运算符、下标运算符、函数调用运算符)必须定义为类的成员函数,有的运算符则不能定义为类的成员函数(如流插入“<<”和流提取运算符“>>”、类型转换运算符)。
3.重载运算符的规则
[1]C++不允许用户自己定义新的运算符,只能对已有的C++运算符进行重载。
[2]C++允许重载的运算符有,
双目算术运算符
|
+(加),-(减),*(乘),/(除),%(取模)
|
关系运算符
|
==(等于),!=(不等于),<(小于),>(大于),<=(小于等于),>=(大于等于)
|
逻辑运算符
|
||(逻辑或),&&(逻辑与),!(逻辑非)
|
单目运算符
|
+(正),-(负),*(指针),&(取地址)
|
自增自减运算符
|
++(自增),--(自减)
|
位运算符
|
|(按位或),&(按位与),~(按位取反),^(按位异或),<<(左移),>>(右移)
|
赋值运算符
|
=,+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=(双目算术运算符与位运算符)
|
空间申请与释放
|
new,delete,new[],delete[]
|
其他运算符
|
()(函数调用),->(成员访问),->*(成员指针访问),,(逗号),[](下标)
|
不能重载的运算符(5个),
.(成员访问运算符),.*(成员指针访问运算符),::(域运算符),sizeof(长度运算符),?:(条件运算符)。
[3]重载不能改变运算符运算对象(即操作数)的个数。如关系运算符“>”和“<”等是双目运算符,重载后仍为双目运算符,需要2个参数。运算符“+”,“-”,“*”,“&”等既可以是单目运算符,也可以是双目运算符,可以分别将它们重载为单目运算符或双目运算符。
[4]重载不能改变运算符的优先级别。例如“*”和“/”优先于“+”和“-”,不论怎么进行重载,各运算符之间的优先级不会改变。
[5]重载不能改变运算符的结合性。如赋值运算符“=”是右结合性(自右向左),重载后仍为右结合性。
[6]重载运算符的函数不能有默认的参数,否则就改变了运算符参数的个数,与前面[3]矛盾。
[7]重载的运算符必须和用户定义的自定义类型对象一起使用,其参数至少应有一个是类对象(或类对象的引用)。也就是说,参数不能全部是C++的标准类型,以防止用户修改用于标准类型数据的运算符的性质。
[8]用于类对象的运算符一般必须重载,但有两个例外,运算符“=”和“&”不必用户重载。
赋值运算符(=)可以用于每个类对象,可以利用它在同类对象之间相互赋值。因为系统为每个新声明的类重载了一个赋值运算符,它的作用是逐个复制类的数据成员。所以用户不必自己进行重载。但如果数据成员中包含指向动态分配内存的指针成员时,在复制此成员时可能出现危险。在这种情况下,就需要自己重载赋值运算符。
地址运算符&也不必重载,它能返回类对象在内存中的起始地址。
4.重载双目运算符
双目运算符(或称二元运算符)有两个参数数,通常在运算符的左右两侧,如3+5,a=b,i<1等。在重载双目运算符时,在函数中应该有两个参数。
下面是一个重载双目运算符的例子,它的用意是比较两个string的大小(使用类库的strcmp函数按字母序进行比较),重载“>”,“<”,“==”对比较得到的值输出。
#include<iostream>
#include<cstring>
using namespace std;
class String{
public:
String(){
p=NULL;
}
String(char*);
friend bool operator>(String&,String&);
friend bool operator<(String&,String&);
friend bool operator==(String&,String&);
private:
char *p;
};
String::String(char *str){
p=str;
}
bool operator>(String &str1,String &str2){
if(strcmp(str1.p,str2.p)>0)
return true;//非0
else return false;//0
}
bool operator<(String &str1,String &str2){
if(strcmp(str1.p,str2.p)<0)
return true;//非0
else return false;//0
}
bool operator==(String &str1,String &str2){
if(strcmp(str1.p,str2.p)==0)
return true;//非0
else return false;//0
}
int main(){
String str1("Ada"),str2("Bob");
cout<<(str1>str2)<<endl;
cout<<(str1<str2)<<endl;
cout<<(str1==str2)<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}
|
5.重载单目运算符
单目运算符只有一个操作数,如!a,-b,&c,*p,--i,++i等。由于单目运算符只有一个操作符,因此运算符重载函数只有一个参数,如果运算符重载函数为成员函数,则可以省略此参数。
下面的例子是对自增“++”运算符的重载,Time类表示时间,当对它的对象进行自增操作时,须达到秒至60时,自动清零并且分加1的效果。
#include<iostream>
#include<cstring>
using namespace std;
class Time{
public:
Time(int m=0,int s=0){
minute=m;
second=s;
}
Time operator++();//前置自增
Time operator++(int); //后置自增
void display(string str){
cout<<str<<"->"<<minute<<":"<<second<<endl;
}
private:
int minute,second;
};
Time Time::operator++(){//前置自增
if(++second>=60){
second-=60;
++minute;
}
return *this;
}
Time Time::operator++(int){ //后置自增
Time temp(*this);//先将当前对象通过复制构造函数临时保存起来
second++;
if(second>=60){
second-=60;
++minute;
}
return temp;
}
int main(){
Time t1(34,59),t2;
t1.display("time1");
++t1;//测试前置自增
t1.display("time1");
t2=t1++;//测试后置自增
t1.display("time1");
t2.display("time2");
system("PAUSE");
return EXIT_SUCCESS;
}
|
C++约定,如果在成员函数中,涉及自增(自减)运算符重载,如果增加一个int形参,就是后置自增(自减)运算符函数,否则为前置自增(自减)运算符函数。
重载后置自增(自减)运算符时,多了一个int型的参数,其目的是为了与前置自增运算符重载函数区别,此外没有任何作用,在定义函数时也不必使用此参数,因此可以省略参数名。编译系统在遇到重载后置自增运算符时,会自动调用此函数。
6.重载流插入和流提取运算符
在类库提供的头文件中已经对“<<”和“>>”进行了重载,使之作为流插入运算符和流输出运算符,能用来输出和输入C++标准类型的数据。
如果用户希望“<<”和“>>”输出和输入自己定义的数据类型,则需要对其重载。对“<<”和“>>”重载的函数形式如下,
istream& operator>>(istream&,自定义类&);
ostream& operator<<(ostream&,自定义类&);
|
只能将重载“<<”和“>>”的函数定义为友元函数或普通函数,而不能将它们定义为成员函数。
#include<iostream>
using namespace std;
class Complex{
public:
Complex(double r=0,double i=0){
real=r;
imag=i;
}
friend istream& operator>>(istream&,Complex&);
friend ostream& operator<<(ostream&,Complex&);
private:
double real,imag;
};
istream &operator>>(istream &in,Complex &c){
cout<<"input real part and imaginary part:";
in>>c.real>>c.imag;
return in;
}
ostream &operator<<(ostream &out,Complex &c){
out<<"("<<c.real<<"+"<<c.imag<<"i)"<<endl;
return out;
}
int main(){
Complex c1,c2;
cin>>c1>>c2;
cout<<c1<<c2;
system("PAUSE");
return EXIT_SUCCESS;
}
|
结果:
input real part and imaginary part:3 4
input real part and imaginary part:9 -2
(3+4i)
(9+-2i)
<<运算符重载函数中的形参out是ostream类对象的引用,形参名是用户定义的。return out的作用是能够连续向输出流插入信息。out是ostream类的对象,它是实参cout的引用。因此,return out就是return cout,将输出流cout的现状返回。
cout<<c1<<c2;表示先处理cout<<c1,即(cout<<c1)<<c2;
>>运算符重载函数同理分析。
这里重提一下引用的作用
[1]利用引用作为函数的形参可以在调用函数的过程中不用值传递的方式,而是通过传址的方式。因此不会生成临时变量(实参的副本),减少了时间和空间的开销。
[2]如果重载函数的返回值是对象的引用时,返回的不是常量,而是引用所代表的对象,它可以出现在赋值号的左侧而成为左值(left value),可以被赋值或参与其它操作(如保留cout流的当前值以便连续使用“<<”输出)。