面向对象程序设计第08章-重载_第1页
面向对象程序设计第08章-重载_第2页
面向对象程序设计第08章-重载_第3页
面向对象程序设计第08章-重载_第4页
面向对象程序设计第08章-重载_第5页
已阅读5页,还剩51页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、第八章 重载,本章概要: “重载”是面向对象程序设计的第三个基本特点。通常,在进行面向对象的程序设计时使用两种形式的重载:函数重载是指在相同作用域内,若干个参数特征不同的函数使用相同的函数名;运算符重载是指同样的运算符可以施加于不同类型的操作数上面。当然,当参数特征不同或被操作数的类型不同时,实现函数的算法或运算符的语义往往是不同的。 函数重载是通过静态联编(也叫先前联编)实现的,即在编译时根据调用函数的实参个数和类型,决定使用该函数名的哪个具体的实现代码;运算符重载也是通过静态联编实现的,同样是在编译时根据被操作数的类型,决定使用该运算符的哪种具体语义。,第八章 重载,本书第五章已经讲述了函

2、数重载,本章着重讲述运算符重载。为了能自由地使用重载了的运算符,往往需要在自定义的数据类型和预定义数据类型之间进行相互转换,或需要在不同的自定义数据类型之间进行相互转换,因此,本章还将讲述类类型转换。具体地说,本章将讲述以下内容:重载的好处,对运算符重载的限制,运算符重载的语法,重载赋值运算符,重载+和-运算符,重载运算符的规则和类类型转换等。,本章快速索引,8.1 重载的好处 8.2 对运算符重载的限制 8.3 运算符重载的语法 8.4 重载赋值运算符 8.5 重载 “+” 和 “-” 运算符 8.6 重载运算符的规则 8.7 类类型转换 8.8 小结,8.1 重载的好处,为什么要重载运算符

3、,如: int sum_i; float sum_f; int i = 135,j = 750; float x = 3.78, y = 97.64; sum_i = i + j; cout “i + j = ” sum_i; sum_f = x + y; cout “x + y = ” sum_f; sum_f = i + j + x; cout “i + j + x = ” sum_f; .,8.1 重载的好处,为什么同一个运算符“ + ”可以用于完成不同类型的数据的加运算呢?原来C+语言针对预定义数据类型已经对某些运算符做了适当的重载。 在编译表达式i + j + x时,因为被操作数类型

4、不同,编译程序首先自动完成类型转换,把i和j转换成浮点数,然后采用浮点数相加的算法计算表达式之值。上述这些工作都是编译程序自动完成的,无需程序员操心。有了针对预定义类型数据的运算符重载,使我们编程时感到十分方便,而且写出的表达式与数学表达式很相似,符合人们的习惯。,8.1 重载的好处,#include stdafx.h class Complex private: float Real; / 定义复数的实部 float Imag; / 定义复数的虚部 public: Complex(); Complex(float Rl, float Im); Complex Add(const Comple

5、x ,8.1 重载的好处,inline Complex Complex:Add(const Complex /编译出错,利用重载机制后,正确 ,8.1 重载的好处,刚才我们定义一个简化的复数类,实现了两个复数的加法运算c3 = c1.Add(c2); 但这里并没有实现运算符“+” 的重载,是通过类中的函数Add实现的。 下面我们通过重新定义复数类Complex,实现 c3 = c1 + c2;形式的运算,即运算符“+” 的重载。,8.1 重载的好处,Complex类的定义: #includestdafx.h class Complex private: float Real; / 定义复数的实

6、部 float Imag; / 定义复数的虚部 public: Complex(); Complex(float Rl, float Im); Complex operator + (const Complex ,8.1 重载的好处,inline Complex Complex : operator + (const Complex ,8.1 重载的好处,总之,重载进一步提高了面向对象软件系统的灵活性、可扩充性和可读性。 为了重载运算符“ + ”,我们定义了一个名为operator + 的成员函数,它与Complex类第一个定义中实现加法的成员函数Add唯一差别就是函数名不同。,8.1 重载的

7、好处,编译器在编译“+”时,首先查看+号两端的操作数类型,因为,c1、c2都是类Complex 的对象,所以再查看在Complex类中是否有重载函数operator + ,若有就执行该函数,完成c3 = c1 + c2;的操作,实现了人们习惯方式的算术运算;否则,编译出错,不能进行重载“+”的运算。请注意, c1 + c2的运算结果正是 operator + 函数的返回值类型Complex类型,若返回值是其他类型,则对c3的复制操作可能出现类型不匹配的编译错误。,8.2对运算符重载的限制,C+语言对运算符重载规定了下述一些限制: 只能重载C+语言中原先已有定义的运算符。 不能重载下列几个运算符

8、: ,*,:,?: 不能改变运算符的操作数个数。 不能改变运算符原有的优先级。 不能改变运算符原有的结合特性。 不能改变运算符对预定义类型数据的操作方式。 对运算符重载的限制就是,不能重载C+语言中原来 就没有的运算符即使是C+语言中原先有定义的运算符, 其中也还有四个运算符不能被重载;,8.3运算符重载的语法,C+语言中运算符重载实际上是通过定义成员 函数或友元函数来完成的。重载后运算符对自定 义类型操作数的处理,实际上是通过函数调用来 完成的,是函数调用的一种特殊形式。我们把用 来重载运算符的成员函数或友元函数,统称为运 算符函数。,8.3.1 用成员函数重载运算符,假设需要针对类X重载运

9、算符“” (代表任何一个被重载的运算符), 则应该在定义类X时,像下面那样说明公有的运算符函数: class X public: Return Type operator(参数表); ; 其中,Return Type为运算符函数的返回值类型,即运算结果的类型; operator为运算符重载时必须使用的关键字,它和被重载的运算符连在一 起,作为运算符函数的专用函数名,务必把该函数说明为公有的;是被 重载的运算符;,参数表中列出该运算符所需要的操作数,如下所述: 重载一元(一目)运算符时,参数表为空。在这种情况 下,当前对象(即,调用该运算符函数的对象)作为该运算 符唯一的操作数。 重载二元(二目

10、)运算符时,参数表中有一个参数。在 这种情况下,当前对象作为该运算符的左操作数,参数作 为右操作数。不难看出,当使用成员函数重载运算符时, 定义运算符函数的方法与定义普通成员函数的方法基本相 同,唯一差别是,运算符函数的名字必须为operator (为任何一个被重载的运算符),而不能由程序员随意为 运算符函数起名字。(续),8.3.1 用成员函数重载运算符,重载后运算符的语义: 一元运算符的语义。 如果针对类X重载了一元运算符,xobj是X类的一个对 象,则xobj或xobj的语义为: xobj.operator() 二元运算符的语义 如果针对类X重载了二元运算符,而xobj1和xobj2是X

11、 类的两个对象,则xobj1xobj2的语义为: xobj1.operator(xobj2),8.3.1 用成员函数重载运算符,读者可能希望将两个字符串进行加减运算,像 两个普通数据成员相加减那么简单,即:c = a + b,这里a、b、c都是字符串。下面的类MyString提 供了重载字符串加法“ + ”(两个字符串的连接)、 字符串与单个字符的减法“ - ”(从字符串中去掉某 个字符)及统计删除的字符个数的功能。,8.3.2 用成员函数重载运算符之例,#include stdafx.h #include string.h #include iostream.h class MyString

12、 char p30; / 说明了一个存放字符串的字符数组 int num; / 用来统计删去的字符个数 public : MyString operator + (MyString str1) /重载运算符 + MyString temp; strcpy(temp.p,p); strcat(temp.p, str1.p); return temp; ,8.3.2 用成员函数重载运算符之例,MyString operator - (MyString str1) /重载运算符 - MyString temp; strcpy(temp.p, p); int i = 0; while(temp.pi

13、!= 0) if(temp.pi = str1.p0) for(int k = i;temp.pk != 0;k +) temp.pk = temp.pk+1; num +; /对对象本身中的num+ i -; i +; temp.num = num; /对象本身的num赋值给temp对象的num return temp; ,8.3.2 用成员函数重载运算符之例,void show() cout p endl; void show_num() cout 共删除字符数: num endl; MyString (char s1) num = 0; strcpy(p, s1); MyString (

14、) num = 0; ;,8.3.2 用成员函数重载运算符之例,/下面给出使用重载后运算符的一个主函数: void main(int argc, char* argv) MyString a(abcdef),b(def); MyString c; cout 字符串a = ; a.show(); cout 字符串b = ; b.show(); c = a + b; cout 字符串a + b = ; c.show(); c = a - d; /被减串可以是a、b或者c cout 字符串a - d = ;/为了能输出d,所以加进行转义 c.show(); a.show_num(); ,8.3.2

15、用成员函数重载运算符之例,运行结果为: 字符串a = abcdef 字符串b = def 字符串a + b = abcdefdef 字符串a - d = abcef 共删除字符数:1,8.3.3 用友元函数重载运算符,需要针对类X重载运算符“”(代表任何一个 被重载的运算符),则在定义类X时像下面那样说 明运算符函数,使它成为类X的友元函数: class X friend ReturnType operator(参数表); ,8.3.3 用友元函数重载运算符,8.3.1节中说明运算符函数使之成为类X的成员 函数的格式相比,主要有以下几点差别: 在函数原型前多了一个关键字friend。 由于是友

16、元函数,因此在类X的公有段和私 有段说明此函数的效果相同,而若作为成员 函数说明运算符函数,则必须放在类X的公有段。,8.3.3 用友元函数重载运算符, 由于这样说明的运算符函数是类X的友元函数而不 是成员函数,因此它没有this指针。 由此可以推知, 必须在参数表中显式列出每一个操作数。因此,用友 元函数重载运算符时参数表中参数个数,比用成员函 数重载运算符时参数表中的参数个数多一个。也就是 说,重载一元运算符时参数表中应该有一个参数;重 载二元运算符时参数表中应该有两个参数,其中第一 个参数作为左操作数,第二个参数作为右操作数。,8.3.3 用友元函数重载运算符,下面介绍用友元函数重载之后

17、,运算符的语义: 一元运算符的语义。 如果在类X中重载了一元运算符,xobj为X 类的一个对象,则xobj或xobj的语义为: operator(xobj) 二元运算符的语义。 如果在类X中重载了二元运算符,而xobj1和 xobj2是X类的两个对象,则xobj1xobj2的语义为: operator(xobj1,xobj2),#includestdafx.h #includeiostream.h class ThreeD int x, y, z; / 定义三维坐标 public: friend ThreeD operator + (ThreeD op1, ThreeD op2); /用友元函

18、数重载+ ThreeD operator = (ThreeD op2); /只能用成员函数重载 = friend ThreeD operator + (ThreeD ,8.3.3 用友元函数重载运算符,/重载运算符= ThreeD ThreeD : operator = (ThreeD op2) x = op2.x; y = op2.y; z = op2.z; return * this; / 重载一元运算符+ ThreeD operator + (ThreeD ,8.3.3 用友元函数重载运算符,void ThreeD : Assign(int i, int j, int k) / 设置坐标

19、值 x = i; y = j; z = k; void main() / 使用重载后的运算符的主函数 ThreeD a, b, c; a.Assign(11, 22, 33); b.Assign(20, 40, 60); a.Show(); b.Show(); c = a + b; c.Show(); c = a + b + c; c.Show(); c = b = a; c.Show(); b.Show(); c +; c.Show(); ,8.3.3 用友元函数重载运算符,上列程序输出如下: 11 22 33 20 40 60 31 62 93 62 124 186 11 22 33 11

20、 22 33 12 23 34,8.4 重载赋值运算符,一般运算符必须在经过重载之后,才能用于操 作自定义类型的数据;如果程序员没有为一个类 重载赋值运算符,编译程序将为这个类生成一个 缺省的赋值运算符。缺省的赋值运算符的语义是, 把源对象(赋值号=右侧的对象)逐域地拷贝到目的 对象(赋值号=左侧的对象)中,也就是把源对象数据 成员值逐个赋给目的对象的对应数据成员。,8.4.1指针悬挂问题,#includestring.h class String char *p; int size; public: String(int sz) p=new charsize=sz; String() del

21、ete p; ; (续下页),voidmain() String s1(10); String s2(20); s1=s2; 上例中,由于s1.p和s2.p具有相同值,都指向s2的字符串,s1.p原先指 向的内存区不仅没有释放,而是被封锁起来无法再用,这就是所谓的指针 悬挂问题。更严重的问题是,由于s1.p和s2.p都指向同一块内存区,当s1和 s2这两个对象生存期结束时,将调用两次析构函数,从而使这块内存被释放 两次,这是一个非常严重的错误。,8.4.1指针悬挂问题,可能有的读者对上面的例子感到不易理解,下面我们再 通过浅显易懂一个小例子来说明什么情况下容易出现指针悬 挂。 void mai

22、n() int *p1 = new int(8); /在内存中分配一个整型数据的空间,并将首地址给P1 int *p2 = new int(9); /在内存中再分配一个整型数据的空间,并将首地址给P2 coutp1n; /输出P1所指向的内存单元的地址 coutp2n; /输出P2所指向的内存单元的地址 cout*p1n; /输出P1所指向的内存单元中的内容 p1 = p2; /将P2赋值给P1,即P1也指向了P2所指向的内存单元 coutp1n;,8.4.1指针悬挂问题,coutp2n; /通过输出可以看到P1、P2指向了同一个地址单元 cout*p1n; cout*p2n; /通过输出可以

23、看到P1、P2指向同一地址单元中的内容 delete p1; /实际上释放的是P2指向的内存单元,但是P1开始所指向的地址 /再也找不回来了(因为我们事先没有保存),因此, /无法释放。这就产生了指针悬挂问题。 delete p2; /再次释放P2指向的内存单元,这将产生严重错误 如何解决这个问题呢?请同学们课后思考。,8.4.1指针悬挂问题,8.4.2重载赋值运算符解决指针悬挂问题,C+语言规定,赋值运算符必须使用成员函数重 载,而且重载赋值运算符的成员函数operator=不能被继 承。 解决上述问题指导思想是:在赋值之前,先释放s1.p 原先指向的内存空间。必须重载赋值运算符,使得对目

24、的对象数据成员指针p的赋值,是把源对象指针p所指向 的内容传递给它,而不是简单地传递指针值。 通过重载赋值运算符,在运算符函数operator= 的 定义中,完成释放工作。如下例所示。,8.4.2重载赋值运算符解决指针悬挂问题,#include class String char *p; int size; public: String(int sz) p=new charsize=sz; String() delete p; void operator=(String / 传递指针所指向的内容 ,8.5重载+和-运算符,早期版本的C+语言在重载运算“+”或“-” 时,不能区分是前缀形式还是后

25、缀形式,这给使 用带来不便。C+语言的新标准规定,为了把运 算符“+”(或“-”)重载为前缀形式,应该把它当作 一元运算符来重载。把运算符“+”(或“-”)重载为 后缀形式,应该把它当作二元运算符来重载。,下面用一个程序例子说明把运算符“+”重载为前缀运算符和后缀运算符的方法: #include stdafx.h #includeiostream.h class Number int num; public: Number(int i) num = i; int operator + () / 前缀运算符 num +; / 先加1 return num; / 后返回 int operator

26、+ (int x) / 后缀运算符 int i = num; / 保存原值 num +; / 后加1 return i; / 返回原值 void print() cout num /n; ;,8.5重载+和-运算符,void main() Number n(100); int i = + n; / 前缀形式 cout i = i n; / 输出i = 101 i = n +; / 后缀形式 cout i = i ,,8.6重载运算符的规则, 如果运算符的操作需要修改当前对象的状态 ,则最 好用成员函数重载。 如果一元运算符的操作数,或二元运算符的左操作 数,有可能是预定义类型的数据,则必须能把

27、该操作 数隐式类型转换为类对象,在这种情况下,该运算符 必须用友元函数重载 ,而不能用成员函数重载。 重载后运算符的含义必须清楚、直观,特别是不能 使读程序者在理解时有二义性。,8.7类类型转换,在C+语言中,类被看作用户自定义的类型,它可以像 系统预定义类型一样进行类型转换。 (1)隐式类型转换 当两个操作数类型不一致时,在算术运算之前级别低的 自动转换为级别高的类型;执行赋值表达式E1=E2时,E1的 结果值需先转换成E1类型之后再赋值。 (2) 显式类型转换 显式类型转换有两种方式: 强制转换法的形式为:(类型名)表达式 函数法的形式为: 类型名(表达式),8.7.1 用构造函数完成类类

28、型转换,为了用构造函数完成类类型转换,类内至少必须定义 一个只带一个参数(没有其他参数,或其他参数都有缺省值) 的构造函数,该构造函数的功能是,从参数类型向该类的 类型转换。 编译程序在分析赋值表达式c3=100时,根据隐式类型转 换规则知道,应该把整型数100转换成c3的类型,于是调用 构造函数Complex(100)完成所需要的类型转换。,8.7.1 用构造函数完成类类型转换,以复数类Complex为例,定义一个能把整型数转换成复数的构造函数: class Complex private: float Real; float Imag; public: Complex(); Complex

29、(int a); / 用于类类型转换 Complex(float Rl, float Im); friend Complex operator + (Complex, Complex); ; Complex : Complex() Real = 0; Imag = 0; Complex : Complex(int a) Real = a; Imag = 0; ,Complex : Complex(float Rl, float Im) Real = Rl; Imag = Im; Complex operator + (Complex c1, Complex c2) Complex temp;

30、temp.Real = c1.Real + c2.Real; temp.Imag = c1.Imag + c2.Imag; return temp; main() Complex c1(10.0, 10.0),c2(-20.0, 30.0); Complex c3 = 100; /转换 Complex c4(15); c4 = 20 + c1; /转换 c2 = 50 + 100; /转换 ,8.7.1 用构造函数完成类类型转换,8.7.2 类类型转换函数,使用构造函数实现类型转换很方便,有以下两个缺点: 由于无法为预定义类型定义构造函数,因此,不能利用 构造函数把自定义类型数据转换成预定义类

31、型数据。 不能在不修改已有类定义的前提下,实现从新定义类型 向已有类型的转换。 为了克服用构造函数实现类型转换的方法的缺点,C+ 语言允许用户在源类中定义类 。,8.7.2 类类型转换函数,定义类类型转换函数的格式如下: SourceType: operator DestType() return DestType类对象; 其中,SourceType为源类类名;DestType为目的 类型(即,要转换成的类型),它既可以是自定义类型 也可以是预定义类型。,8.7.2 类类型转换函数,关于类类型转换函数,有以下几点注意事项: 必须把类类型转换函数定义成源类的成员函数,而不能 定义成友元函数。 类

32、类型转换函数没有参数。 与构造函数类似,不显式给出返回值类型。 函数名为operator DestType,即用函数名表明它的返回值类型。 函数体中必须有return DestType类对象的语句,即必须 送回目的类型数据作为函数的返回值。,8.7.2 类类型转换函数,下面以分数类Fraction的定义和使用为例,具体说明怎样用类类型转换 函数实现类类型转换: class Fraction int Numerator; / 分子 int Denominator; / 分母 public: Fraction(int num, int den = 1); operator double() const; / 类类型转换函数 ; Fraction : Fraction(int num, int den) if(den = 0) den = 1; / 分母不能为0 Numerator = num; Denominator = den; ,8.7.2 类类型转换函数,Fraction : operator double() const return (double)Numerator/(double)Denominator; void main() Fraction frc(2); frc = 5; / 用构造函数转换 double d; d = frc.operator do

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论