第六章 运算符重载.docx
《第六章 运算符重载.docx》由会员分享,可在线阅读,更多相关《第六章 运算符重载.docx(54页珍藏版)》请在冰点文库上搜索。
![第六章 运算符重载.docx](https://file1.bingdoc.com/fileroot1/2023-4/30/9a356681-df10-467b-a38e-a2b9f519e7b9/9a356681-df10-467b-a38e-a2b9f519e7b91.gif)
第六章运算符重载
第六章运算符重载
C++为预定义数据类型提供了丰富的运算符集,以简洁明确的方式操作数据。
C++类可以有一个特殊的函数成员,即运算符函数。
程序员也可以将运算符用于操作自定义的数据类型,这需要定义运算符重载函数。
本章讨论运算符重载的语法和使用,还讨论与运算符函数较为类似的类型转换函数。
●本章主要目标
⏹弄清为什么要进行运算符重载
⏹学习运算符重载为成员函数和重载为友元函数的概念
⏹学习运算符重载函数的应用
⏹学习重载++和--运算符、重载赋值=运算符和重载下标[]运算符
⏹学习类型转换概念
●本章重点
⏹运算符重载为成员函数和重载为友元函数的概念。
⏹常用运算符重载。
6.1运算符重载的概念
实际上C++中的许多运算符,例如“+”可以用于int类型数据,也可用于double类型数据,这时,“+”运算符具有两种不同的解释(实现代码)。
也就是说,像“+”这样的运算符已经被系统重载了。
但是,这种重载只适用于系统预定义的数据类型,而对于用户自定义的数据类型就行不通了。
比如,程序员要定义复数这种不是C++的预定义的数据类型:
classcomplex
{
private:
intreal,imag;//定义复数的实部和虚部
public:
complex(intx=0,inty=0)
{real=x;imag=y;}
};
如果对复数做加法操作:
complexc1(1,2),c2(3,4),c3;
c3=c1+c2;//这是错误的,“+”不识别complex数据类型
因为complex数据类型是用户自定义类型,所以上面的加法操作会导致编译错误。
为了解决这个问题,当然我们可以变通一下,在complex类定义中增加一个加法的成员函数:
classcomplex
{
private:
intreal,imag;//定义复数的实部和虚部
public:
complex(intx=0,inty=0)
{real=x;imag=y;}
complexadd(complexc)
{
complext;
t.real=real+c.real;
t.imag=imag+c.imag;
returnt;
}
};
然后再做两个复数的加法:
complexc1(1,2),c2(3,4),c3;
c3=c1.add(c2);
虽然这样也可以达到同样的目的,但是这远没有前面用c3=c1+c2来的更直观和符合我们的习惯。
对用户来讲更希望采用后者,所以运算符重载就变得很重要了,那么,下面我们就给运算符重载下一个简单的定义,所谓运算符重载就是在系统预定义的含义基础上再赋予用户自定义的数据类型进行操作的新的含义。
这便增强了C++语言的扩充能力。
6.1.1如何重载运算符
首先我们来看前面提到的关于两个复数的加法,有两种解决方案,一种是定义了成员函数add(complexc)实现该操作,使用c1.add(c2)表示对加法函数的调用,不太直观。
第二种方案就是重载运算符+使两个复数相加表示为c1+c2,那么如何才能实现这样的效果呢?
我们可以把成员函数add(complexc)改为对运算符+进行重载的成员函数,而重载运算符为成员函数要求在运算符前面加一个关键字operator,在类中实现其语法形式为:
函数类型operator<运算符>([<参数表>])
{
//函数体
}
【例6.1】在复数complex类中重载运算符+。
#include
classcomplex
{
private:
intreal,imag;//定义复数的实部和虚部
public:
complex(intx=0,inty=0)
{real=x;imag=y;}
complexoperator+(complexc)//定义+运算符重载函数
{
complext;
t.real=real+c.real;//这里的+采用系统预定义含义
t.imag=imag+c.imag;
returnt;
}
};
voidmain()
{
complexc1(1,2),c2(3,4),c3;
c3=c1+c2;//这是函数operator+(…)隐式调用形式
}
程序中的c1+c2就是对函数operator+(complexc)的调用,也可以写成c1.operator+(c2)的形式,这两种形式是等价的,我们把c1+c2这种形式称为隐式调用形式,而c1.operator+(c2)的形式称为显式调用形式,一般我们采用习惯的方式也就是c1+c2这种隐式调用形式来调用。
另外我们要注意到:
t.real=real+c.real;//这里的+采用系统预定义含义
c3=c1+c2;//这里的+采用类中重载后的含义
那么到底在什么时候采用系统预定义含义又在什么时候采用类中重载后的含义呢?
关键要看+号两边的操作数是系统预定义类型还是新定义的类的对象,如果操作数是系统预定义类型那么+就表示系统预定义含义,反之,如果操作数是自定义的类的对象则+就表示类中重载后的含义,当然除了+运算符之外对于其它重载的运算符也有类似的性质。
6.1.2重载运算符的语法形式
在C++中,运算符重载函数的实现除了可以采用成员函数的形式,还可以采用友元函数的形式,上例中的运算符重载函数的实现采用的是成员函数形式,下面我们给出两种形式的比较:
(1)运算符重载为成员函数的语法形式是
<返回类型>operator<运算符>([<参数表>])
{
//函数体
}
其中operator是运算符重载函数的关键字,它后面要跟所重载的运算符符号。
由
“operator运算符”作为一个整体称为运算符重载函数的函数名。
比如我们重载运算符“+”,
operator+就是“+”运算符重载函数的函数名。
(2)运算符重载为友元函数的语法形式是
<返回类型>operator<运算符>(<参数表>)
{
//函数体
}
当然,上面这个运算符重载函数是应该以友元函数声明在某个用户自定义类型中的,如下所示:
classm
{
…
friend<返回类型>operator<运算符>(<参数表>);
…
};
这样,这个运算符重载后的运算符就获得了新的含义,它可以对类m的对象进行操作,换句话说,类m的对象可以以新的含义使用被重载了的运算符。
除此之外,该运算符仍然保持原来的含义。
重载运算符函数一般定义为类的公有的非静态的成员函数或类的友元函数。
使用类外的非成员函数(即友元函数)重载运算符,主要是能直接访问类对象的私有成员。
这两种方式非常相似,但还有一些差别,其关键原因是成员函数具有this指针,而友元函数没有this指针。
1.单目运算符
单目运算符,不论是前缀还是后缀,都需要一个操作数。
对任意一个运算符@,两种重载方式说明如下。
(1)成员函数重载运算符:
typeclass_name:
:
operator@()
{
…
}
设obj为class_name的类对象。
显式调用形式:
obj.operator@()
隐式调用形式:
@obj或者obj@
单目运算符重载函数operator@所需的一个操作数通过this指针隐含的传递。
因此它的参数表为空。
(2)友元函数重载运算符:
typeoperator@(class_nameobj)
{
…
}
显式调用形式:
operator@(obj)
隐式调用形式:
@obj或者obj@
单目运算符重载友元函数operator@所需的一个操作数由参数表给出。
无论运算符@重载为类的成员函数还是友元函数,隐式调用方式都是相同的。
例:
#include
classcomplex//复数类声明
{
public:
//外部接口
complex(doubler=0,doublei=0){real=r;imag=i;}//构造函数
friendcomplexoperator!
(constcomplex&c1);
voiddisplay();//输出复数
private:
//私有数据成员
doublereal;//复数实部
doubleimag;//复数虚部
};
complexoperator!
(constcomplex&c1)//重载运算符函数实现
{
complextemp;
temp.real=-c1.real;
temp.imag=-c1.imag;
returntemp;
}
voidcomplex:
:
display()
{
cout<if(imag>=0)cout<<"+"<elsecout<cout<<"i"<}
voidmain()//主函数
{
complexc1(1,2),c2;//定义复数类的对象
cout<<"c1=";c1.display();
cout<<"c2=";c2.display();
c2=!
c1;//使用重载运算符完成复数取!
c2.display();
}
对于单目运算符,只有一个操作数,当重载为成员函数时,没有参数,当重载为友元函数时,一个参数;
2.双目运算符
双目运算符需要两个操作数。
(1)成员函数重载运算符的形式:
typeclass_name:
:
operator@(class_nameobj2)
{
…
}
显式调用形式:
obj1.operator@(obj2)
隐式调用形式:
obj1@obj2
双目运算符重载成员函数operator@所需的第一个操作数通过this指针隐含的传递。
第二个操作数通过参数提供,因此他只有一个参数。
(2)友元函数重载运算符的形式:
typeoperator@(class_nameobj1,class_nameobj2))
{
…
}
显式调用形式:
operator@(obj1,obj2)
隐式调用形式:
obj1@obj2
双目运算符重载友元函数operator@所需的两个操作数都通过参数提供,因此他有两个参数。
不管是用成员函数还是友元函数重载运算符,该运算符的隐式调用方法是相同的。
用成员函数重载运算符和用友元函数重载运算符,他们传递参数的方法不一样,成员函数是用this指针隐式地访问类对象的某个参数,友元函数的调用必须明确地列出该参数。
这就导致了他们的实现代码不相同。
运算符重载何时用成员函数,何时用友元函数?
表面上看,没有什么理由一定要用户必须选择成员函数,或必须选择友元来实现某类对象的操作。
然而,成员函数仅仅能为一个“实际对象”所调用,友元无此限制。
因此,一般要遵循以下规则:
(1)若运算符的操作需要修改类对象的状态,则它最好应该是成员函数,而不应是友元,例如需要左值操作数的运算符(如+=,++)的重载最好用成员函数,所谓左值操作数,简而言之就是赋值运算符左边的操作数,比如表达式a+=b,a就是左值操作数。
如果用友元函数,则需要被修改的类对象所对应的参数必须是该类对象的引用。
(2)相反,如果运算符所需的操作数(尤其是第一个操作数)是一个不同类的对象或者是一个内部类型的对象希望有隐式类型转换,则该运算符重载必须用友元,而不用成员函数。
(3)注意:
=,(),[],->不能使用友元函数进行重载。
其余的运算符都可以使用友元函数来实现重载。
许多人偏爱友元函数的表达风格,宁肯用友元的表达方式,也不愿用成员函数的表达方法。
但有些特殊的情况,选择成员函数比较好。
成员函数调用语法使用户清楚地知道对象是否可修改,而友元函数所采用的参数在这方面是非常不明显的;成员函数能隐含this指针,而友元必须至少带一个显式参数,成员函数方式表达较为简洁。
用户可根据具体情况进行选择。
另外,友元函数破坏了封装机制,建议尽量使用成员函数实现操作。
6.1.3重载运算符的限制
只有在C++预定义的运算符集中的运算符才可以被重载,类的设计者不能声明一个不是C++预定义的重载运算符,例如,如果声明了一个重载运算符operator**来提供指数算法,就会产生编译错误,因为**不是C++预定义的运算符。
C++中大部分预定义的运算符都可以重载。
下面列出可以重载的运算符:
+-*/%^&|~
!
=<>+=-=*=/=%=
^=&=|=<<>>>>=<<===!
=
<=>=&&||++--->*‘->
[]()newdeletenew[]delete[]
当然不是所有C++预定义的运算符都可以重载,有几个特殊的C++运算符不能被重载:
类属关系运算符“.”、成员指针运算符“.*”、作用域分辨符“:
:
”、三目运算符“?
:
”和sizeof运算符。
前面两个运算符保证了C++中访问成员功能的含义不被改变。
作用域分辨符合sizeof运算符的操作数是类型,而不是普通的表达式,也不具备重载的特征。
另外,运算符重载还有以下一些限制:
(1)对于预定义类型的运算符,他的预定义意义不能被改变。
例如,整形加运算符不能被重载为其他的含义:
intoperator+(int,int);//错误:
不能为int重新定义运算符
(2)也不能为预定义数据类型定义其他的运算符。
例如,有两个数组类型操作数的operator+不能被加入到预定义操作集中。
程序员只能为用户自定义数据类型的操作数定义重载运算符。
(3)重载运算符不会改变原运算符的优先级和结合性,也不能改变操作数的个数,也就是说单目运算符重载后只能还是单目运算符,双目运算符重载后也只能还是双目运算符。
(4)重载运算符含义必须清楚,最好不要偏离原来的含义,比如“+”运算符重载为乘法的功能就很难让人接受。
6.2重载运算符函数应用
前面例6.1给出了一个简单的用成员函数形式重载的复数类,这一节我们给出用成员函数重载的完整的复数类,包含“+”、“-”、“*”、“/”四个运算符。
【例6.2】用成员函数重载运算符的完整的复数类。
#include
classcomplex//复数类声明
{
public:
//外部接口
complex(doubler=0,doublei=0){real=r;imag=i;}//构造函数
complexoperator+(complexc2);//运算符+重载成员函数
complexoperator-(complexc2);//运算符-重载成员函数
complexoperator*(complexc2);//运算符*重载成员函数
complexoperator/(complexc2);//运算符/重载成员函数
voiddisplay();//输出复数
private:
//私有数据成员
doublereal;//复数实部
doubleimag;//复数虚部
};
complexcomplex:
:
operator+(complexc2)//重载运算符+函数实现
{
doubler=c2.real+real;
doublei=c2.imag+imag;
returncomplex(r,i);
}
complexcomplex:
:
operator-(complexc2)//重载运算符-函数实现
{
doubler=real-c2.real;
doublei=imag-c2.imag;
returncomplex(r,i);
}
complexcomplex:
:
operator*(complexc2)//重载运算符*函数实现
{
doubler=real*c2.real-imag*c2.imag;
doublei=imag*c2.real+real*c2.imag;
returncomplex(r,i);
}
complexcomplex:
:
operator/(complexc2)//重载运算符/函数实现
{
doubler=(real*c2.real+imag*c2.imag)/(c2.real*c2.real+c2.imag*c2.imag);
doublei=(imag*c2.real-real*c2.imag)/(c2.real*c2.real+c2.imag*c2.imag);
returncomplex(r,i);
}
voidcomplex:
:
display()
{
cout<if(imag>=0)cout<<"+"<elsecout<cout<<"i"<}
voidmain()//主函数
{
complexc1(1,2),c2(3,4),c3,c4;//定义复数类的对象
cout<<"c1=";c1.display();
cout<<"c2=";c2.display();
c3=c1+c2;//使用重载运算符完成复数加法
cout<<"c3=c1+c2=";
c3.display();
c4=c1+c2+c3;//使用重载运算符完成复数加法
cout<<"c4=c1+c2+c3=";
c4.display();
c3=c1-c2;//使用重载运算符完成复数减法
cout<<"c3=c1-c2=";
c3.display();
c3=c1*c2;//使用重载运算符完成复数乘法
cout<<"c3=c1+c2=";
c3.display();
c3=c1/c2;//使用重载运算符完成复数除法
cout<<"c3=c1+c2=";
c3.display();
}
程序运行结果如下:
c1=1+2i
c2=3+4i
c3=c1+c2=4+6i
c4=c1+c2+c3=8+12i
c3=c1-c2=-2-2i
c3=c1*c2=-5+10i
c3=c1/c2=0.44+0.08i
在表达式c1+c2中,调用运算符的对象是c1,运算符右边的对象c2被作为参数传递给函数。
因而表达式c1+c2可解释为:
c1.operator+(c2)
用成员函数重载运算符,调用运算符重载函数的对象都是由this指针隐含传递的。
重载运算符“+”的另一关键之处在于它的返回类型为complex。
虽然该函数可以返回任何合法的C++类型,但它若返回一个complex对象,可以使+运算符被用在诸如c1+c2+c3这样较复杂的表达式中,其中c1+c2返回一个complex对象然后和c3相加,否则,该表达式会出错。
【例6.3】用成员函数重载运算符的字符串类。
#include
#include
#include
classstring
{
char*s;
public:
string()
{
charss[10];
cin>>ss;
s=newchar[strlen(ss)+1];
strcpy(s,ss);
}
string(string&ss)
{
s=newchar[strlen(ss.s)+1];
strcpy(s,ss.s);
}
~string()
{
delete[]s;
}
char*getstring()
{
returns;
}
voiddisplaystring()
{
cout<
}
//字符串连接
stringoperator+(strings2)
{
unsignedintj=strlen(s);
char*t=new[j+1];
strcpy(t,s);
unsignedintk=strlen(s2.s)
s=newchar[j+k+1];
for(unsignedinti=0;i<=j;i++)
*(s+i)=*(t+i);
for(i=0;i<=k;i++)
*(s+j+i)=*(s2.s+i);
*(s+j+i)=’\0’;
delete[]t;
return*this;
}
//字符串比较==
intoperator==(strings2)
{
unsignedintj=(strlen(s2.)<=
strlen(s)?
strlen(s2.s):
strlen(s));
unsignedinti=0;
if(strlen(s)!
=strlen(s2.s))
return0;
else
while((*(s+i)==*(s2.s+i))&&(i{i++;}
if(i==j)
return1;
else
return0;
}
};
voidmain()
{
cout<<"输入string类对象s1:
";
strings1;
cout<<"输入string类对象s2:
";
strings2;
//测试字符串逻辑相等
getchar();//程序在此暂停,按enter键继续
cout<<"
(1)测试字符串s1和字符串s2逻辑相等(重载逻辑操作符==)"<cout<<"测试结果:
"<if(s1==s2)
cout<<"两个字符串相等,返回值1"<else
cout<<"两个字符串不相等,返回值0"<//测试字符串逻辑小于
getchar();//程序在此暂停,按enter键继续
cout<<"
(2)测试字符串s1和字符串s2逻辑小于(重载逻辑操作符<)"<cout<<"测试结果:
"<//测试字符串连接
getchar();//程序在此暂停,按enter键继续
cout<<"(4)测试字符串s1和字符串s2的连接(重载操作符+号)"<cout<<"测试结果:
"<cout<<"s1+s2=";
s1+s2;
s1.displaystring();
}
程序运行结果如下:
输入string类对象s1:
aaa
输入string类对象s2:
bbb
(1)测试字符串s1和字符串s2逻辑相等(重载逻辑操作符==)
测试结果:
两个字符串不相等,返回值0
(4)测试字符串s1和字符串s2的连接(重载操作符+号)
测试结果:
s1+s2=aaabbb
pressanykeytocontinue