第四部分编译时多态教学课件.ppt

上传人:sccc 文档编号:5146620 上传时间:2023-06-08 格式:PPT 页数:121 大小:407.03KB
返回 下载 相关 举报
第四部分编译时多态教学课件.ppt_第1页
第1页 / 共121页
第四部分编译时多态教学课件.ppt_第2页
第2页 / 共121页
第四部分编译时多态教学课件.ppt_第3页
第3页 / 共121页
第四部分编译时多态教学课件.ppt_第4页
第4页 / 共121页
第四部分编译时多态教学课件.ppt_第5页
第5页 / 共121页
点击查看更多>>
资源描述

《第四部分编译时多态教学课件.ppt》由会员分享,可在线阅读,更多相关《第四部分编译时多态教学课件.ppt(121页珍藏版)》请在三一办公上搜索。

1、第四章 编译时多态性,封装性是面向对象程序设计的基础,可以说没有封装性就没有面向对象的程序设计。封装性从根本上解决了数据的安全性,也为实现不同数据的操作同一性奠定了基础,这种操作的同一性反映了客观世界中规范不同对象行为的一致性需要,它构成了面向对象程序设计多态性的一部分。不过这种多态性必须预先确定操作所施加的数据类型,因此不是完备意义上的多态性,而只是基于对象的多态性。这种不同数据的操作同一性必须在程序编译时静态确定,故称为编译时多态性,或称为静态多态性。实现这种静态多态性的途径是:函数重载和运算符重载。,本章要点1 函数重载2 运算符重载3 使用成员函数重载运算符4 使用友元函数重载运算符5

2、 自增+和自减-运算符的重载6 调用运算符()和下标运算符 的重载7 动态内存管理运算符的重载8 赋值运算符重载9 输入输出运算符重载10 类型转换(函数),4.1 函数重载 函数重载为实现不同数据的操作同一性提供了根本编程机制。函数重载的基本规则:函数名必须相同:体现了操作同一性。函数的参数必须不同:体现了同一操作的实现差异 和所施加的数据差异。在面向对象的程序中函数重载表现在两个方面:类外全局函数重载 类成员函数重载,4.1.1 类外全局函数重载 在面向对象程序设计中,全局函数常常用来作为类的友元函数,因此全局函数重载可以用来实现不同类对象的同一类外操作。关于全局函数重载的方法已经在第二章

3、中讲述,本章不再赘述。为了进一步理解函数重载是如何在编译时确定同一操作施加于不同类型的数据,我们模拟分析 C+编译器如何采用“名子压延”的方法实现重载函数的调用。所谓“名子压延”是指编译器编译重载函数调用时,先将原重载函数名和参数类型结合起来,以创建新重载函数名。然后用新重载函数名替代原重载函数名。例如,一个程序中有两个重载函数,原型声明为:,int myAns(float x,int j);int myAns(int I,char c);编译时,编译器首先压延修改这两个重载函数名。修改后的新函数名或许会变成如下形式:int myAnsFLTINT(float x,int j);int myA

4、nsINTCHAR(int i,char c);这样,就把原来无法区分开来的相同的函数名变成可以区分的不同的函数名。例如:调用 myAns(float x,int j)时,则调用 myAnsFLTINT(float x,int j);而调用 myAns(int I,char c)时,则调用 myAnsINTCHAR(int i,char c)。,假如调用这两个函数的语句为:exam1=myAns(15.3,15);exam2=myAns(45,a);用新函数名替代原函数名后,使得调用这两个函数的语句会发生如下相应的改变:exam1=myAnsFLTINT(15.3,15);exam2=myAn

5、sINTCHAR(45,a);,注意,上述对函数名的具体压延过程和结果只是为了便于叙述假定的,并非 C+编译器对这两个函数名的真实压延结果。,4.1.2 类成员函数重载 类成员函数重载分为两类:1 构造函数重载 为类对象的创建和类对象的数据成员赋初值提供不 同的方法。重载构造函数的参数必须不同。2 公有成员函数重载 提供响应相同消息的不同接口方法。这类重载有两 种情况:同一类中的重载成员函数的参数必须有差别。派生类中分属于基类和派生类的重载成员函数的 参数可以相同,但基类的重载成员函数被覆盖。例如:,class point int x,y;public:point(int a,int b)x=

6、a;y=b;float area()return 0.0;class circle:public point int radius;public:circle(int x,int y,int rad):point(x,y)radius=rad;float area()/重载成员函数 return float(3.1416)*float(radius)*float(radius);,main()point p(20,20);circle c(8,8,30);cout p.area()endl;cout c.area()endl;cout c.point:area()endl;return 0;其

7、中 area 是参数相同的重载成员函数。编译过程 中区别这两个不同版本的 area 的情况有两种:,使用对象名只能分别调用基类和派生类的参数 相同的重载成员函数。例如:p.area()将调用 point:area()方法;c.area()将调用 circle:area 方法。在对象名后添加“基类名:”可以使用派生类对 象名可以调用基类的参数相同的重载成员函 数。例如:c.point:area()将调用 point:area()方法。返回,4.2 运算符重载运算符可以视为是一种特殊的函数,它们可以用一种简洁的,接近自然语言的表达式方式被调用。C+拥有一系列的预定义运算符,这些运算符能对所有的预定

8、义类型的数据进行形式一致的操作。也就是说,可以使用预定义类型的数据作参数调用这些运算符函数。例如,加运算符+:int x,y;float e,f;y=x+y;f=e+f;,这种对于不同类型的数据使用相同的表达式调用相同的运算符正是通过重载机制实现的。但这些运算符却不能自动施加在自定义类型的对象。例如:自定义的复数类 complex 的定义如下:class complex double real,imag;public:complex(double r=0,double i=0)real=r;imag=i;,main()complex com1(1.1,2.2),com2(3.3,4.4),to

9、tal;total=com1+com2;/“+”运算符无法对复数进行操作 return 0;产生上述错误的原因很显然,因为运算符“+”允许的操作数据类型中不包括 complex 类型,所以编译器就不知道调用该运算符的哪个重载版本来完成相应的操作。,解决的办法就是扩展“+”运算符允许操作的数据类型,即为复数类 complex 定义自己的“+”运算符操作。C+的运算符重载方法为设计不同类对象的同一行为提供了非常有效和方便的手段。运算符重载是通过对运算符函数的重载实现的,运算符函数重载的原型和定义的一般形式如下:函数类型 operator(参数表列);/是运算符名的通配符号函数类型 operator

10、(参数表列)重载操作代码,例如,对复数类 complex 的“+”运算符重载:class complex double real,imag;public:complex(double r=0,double i=0)real=r;imag=i;complex operator+(complex);complex complex:operator+(complex,4.2.1 重载运算符的规则 C+不允许定义新的运算符,只能对系统预定义运 算符进行重载。C+的预定义运算符中允许重载的包括:,不允许重载预定义运算符的包括:重载不能改变预定义运算符函数的参数(操作数)个数。双目运算符重载后仍为双目运算

11、符,单目运 算符重载后仍为单目运算符。重载不能改变预定义运算符的原有优先级。重载不能改变预定义运算符的原有结合律。重载运算符函数的参数不允许有缺省值。否则编译 器会认为是改变了运算符函数的参数个数。,重载的运算符必须与用户自定义类型的对象一起使 用,因此重载运算符函数的参数中至少应该有一个 是自定义类对象或类对象的引用。换句话说,重载 运算符函数的参数不能全部是预定义类型对象,防 止用户修改预定义运算符的性质。例如:int operator+(int a,int b)return a b;显然,这是绝对不允许的。对于双目运算符,允许两个参数都是自定义类对 象,例如两个复数的加运算;也允许一个参

12、数是自 定义类对象,另一个参数是预定义类型对象,例如 一个复数与一个实数的加运算。,complex operator+(double d,complex 系统会为每个自定义类缺省重载了赋值运算符“=”和取地址运算符“&”,其他运算符都需要根据需要进行重载定义。其中:赋值运算符“=”用于同类对象之间的数据成员赋值操作。一般情况下,缺省重载的赋值运算符“=”可以满足要求,但遇到类的数据成员中包含了动态数据指针,则使用缺省重载赋值运算符就可能发生危险,因此必须重载新的赋值运算符。取地址运算符“&”用于计算返回对象在内存中的起始地址。,虽然可以任意定义重载运算符的操作功能,但应该 使重载运算符的功能类

13、似于被重载运算符作用于预 定义类型数据时的操作功能。运算符重载函数可以是运算所施加类对象的成员函 数,也可以是该类的友元函数,如果不需要访问类 的私有数据成员,还可以是既非类成员函数也非友 元函数的普通函数。,在 Java 中虽然也不乏运算符重载的例子,例如:String str=hello+there;该表达式相当于在 C+中的表达式:string str=hello+there;上述表达式都是因为对 Java 的 String 类型和 C+的 string 类型的进行了“+”运算符重载的结果。但要注意的是在 Java 中不允许用户进行运算符重载。返回,4.3 使用成员函数重载运算符1 使用

14、成员函数重载运算符的语法形式 在类定义体中声明要重载的运算符成员函数 type 运算符函数类型;operator 运算符函数名关键字;要重载的运算符名;参数表 被重载运算符所需的右操作数。参数个数=运算符所需操作数个数-1,缺省 的左操作数必须是运算符所属类对象。例如:,type operator(参数表);,class complex double real,imag;public:complex operator+(complex,type 类名:operator(参数列表),重载运算符的使用 运算符的左操作数必须是该运算符成员函数所属 类的对象。双目运算符 例如:complex com1

15、,com2,com3;com3=com1+com2;或 com3=com1.operator+(com2);,表达式形式:对象名 参数对象名;函数形式:对象名.operator(参数对象名);,单目运算符 例如:complex com;+com;com+;或 com.operator+();com.operator+(0);,表达式形式:对象名;对象名;函数形式:对象名.operator();对象名.operator(0);,2 用成员函数重载运算符的使用实例例4-1 定义了一个表达三维空间位置的简单类 three_d,在此类中含有三维空间位置的坐标。通过运算符重载来实现对此类对象的+、和=运

16、算。注意:定义中+和 运算符函数的返回值不应是被加对象和被减对象,而=运算符函数的返回值必须是被赋值的对象。这都是运算符的操作含义所决定的。返回,4.4 使用友元函数重载运算符1 使用友元函数重载运算符的语法形式 在类定义体中声明重载运算符友元函数 friend 友元函数关键字 type 运算符函数类型;operator 运算符函数名关键字;要重载的运算符名;参数表 被重载运算符所需的操作数。参数个数=运算符所需操作数个数。例如:,friend type operator(参数表);,class point int x,y;public:friend point operator+(point

17、,type operator(参数表),重载运算符的使用 双目运算符 例如:point pt1,pt2,pt3;pt3=pt1+pt2;或 pt3=operator+(pt1,pt2);,表达式调用:参数对象名1 参数对象名2;函数调用:operator(参数对象名1,参数对象名2);,单目运算符 例如:point pt;+pt;pt+或 operator+(pt);operator+(pt,0);,表达式调用:参数对象名;参数对象名;函数调用:operator(参数对象名);operator(参数对象名,0);,2 使用友元函数重载运算符的应用实例例4-2 使用友元函数重载运算符的方法实现复

18、数的四则运算,复数运算规则如下:(a+bi)+(c+di)=(a+c)+(b+d)i;(a+bi)-(c+di)=(a-c)+(b-d)i;(a+bi)*(c+di)=(ac-bd)+(bc+ad)i;(a+bi)/(c+di)=(ac+bd)+(bc-ad)i)/(c2+d2);定义一个复数类 complex,并声明和定义相应的友元函数,用于重载运算符+、-、*、/。,例4-3 使用友元函数重载运算符的方法实现集合运算。集合可以用数组或链表表示,在该数组或链表中不允许包含重复元素。定义整型数集合 set,元素个数用整型变量 card 表示。集合的操作包括向集合中追加元素、显示集合的全部元素以

19、及使用友元函数重载运算符的方法实现的集合的主要运算,这些运算包含:判定某一个元素属于集合的运算符&;判定两个集合相等的运算符=;,判定两个集合的不等于运算符!=;两个集合的交运算符*;两个集合的并运算符+;判定某集合是另一集合的子集运算符=;判定某集合是另一集合的纯子集运算符。,4.4.1 两种运算符重载方法的比较1 参数数目:双目运算符 重载运算符的成员函数只有一个参数,用于 表示表达式的右操作数。重载运算符的友元函数有两个参数,分别用 于表达式的左、右操作数 单目运算符 重载运算符的成员函数无参数。重载运算符的友元函数带有一个参数,用于 表示表达式的唯一操作数。,2 无论是使用成员函数还是

20、友元函数重载的运算符,调用它们的表达式形式是一致的,而调用它们的函 数形式是有差别的。,3 大部分运算符重载函数既可以是成员函数,又可以 是友元函数。究竟选择哪一种好呢?要视实际情况 和程序员的习惯。但是应注意以下情况:对于双目运算符,如果使用成员函数重载,则在 有些情况下会产生操作数类型错误。例如:class complex double real,imag;public:complex operator+(double x);complex complex:operator+(double x)return complex(real+x,imag);,对 complex 的对象 com 进

21、行如下的+运算:com=com+100.0;由于对象 com 是+运算符的左操作数,所以它调 用了 complex 类重载的+运算符,把实数 100.0 加 到 com 的实部数据成员 real 上。如果按照+运算 符操作数的交换律,将上面的表达式写成:com=100.0+com;则会引起操作数类型错误,无法完成表达式所要 求的操作。这是因为 complex 类+运算符的左操 作数是 complex 类对象,而表达式的左操作数是 实数,无法调用 complex 类+运算符函数。,如果使用两个友元函数来替换上述 complex 类的+运算符重载:friend operator+(complex

22、com,double x);friend operator+(double x,complex com);complex operator+(complex com,double x)return complex(com.real+x,imag);complex operator+(double x,complex com)return complex(x+com.real,imag);则可避免上述问题,因为友元运算符函数的两个 操作数都是显式地传递给运算符函数的,能满足 加运算操作数的交换律。,所以一般建议使用友元函数重载双目运算符,特 别是期望双目运算符的左操作数的类型能够隐式 转换的情况

23、,则重载双目运算符必须使用友元函 数,而不能使用成员函数。若一个运算符需要修改其操作所施加对象(特别 是该对象为该运算符的第一操作数)的状态,则 选择使用成员函数重载运算符不破坏类对象的封 装性,更符合面向对象程序设计的原则。返回,4.5 自增+和自减-运算符的重载 从运算操作的正确性考虑,使用成员函数还是使用友元函数重载自增运算符+和自减运算符-的效果是一致的。但从面向对象设计原则出发,类对象的自增和自减运算实际上是对被封装的类数据成员依次进行的。因此,建议使用成员函数实现自增运算符+和自减运算符-的重载。使用+和-的表达式形式分为前缀和后缀两种形式,这两种形式对操作数的最终修改虽然相同,但

24、运算符函数的返回值不同,前缀形式返回修改后的操作数,而后缀形式返回修改前的操作数。例如:,int i=0,j=0;i+;+j;cout i,j endl;/显示1,1 cout i+,+j endl;/显示1,2 在 C+2.1 及以后的版本中,编译器可以通过判别在自增或自减运算符函数的参数列表中是否增加了一个附加的整型 int 形式参数来区分所重载的自增或自减运算符可以用于前缀表达式还是用于后缀表达式。,1 自增运算符“+”使用成员函数重载 原型 前缀形式:类名 operator+();后缀形式:类名 operator+(int);例如:class point int x,y;public:

25、point operator+();point operator+(int);,定义 前缀形式:类名 类名:operator+()后缀形式:类名 类名:operator+(int)例如:point point:operatot+()+x,+y;return*this;point point:operatot+(int)point temp(x,y);+x,+y;return temp;,调用 前缀表达式形式:+类对象名;后缀表达式形式:类对象名+;例如:point pt;+pt;/前缀表达式 pt+;/后缀表达式 前缀函数形式:对象名.operator+();后缀函数形式:对象名.operat

26、or+(0);例如:point pt;pt.operator+();/前缀函数形式 pt.operator+(0);/后缀函数形式,2 自减运算符“-”使用成员函数重载 原型 前缀形式:类名 operator-();后缀形式:类名 operator-(int);例如:class point int x,y;public:point operator-();point operator-(int);,定义 前缀形式:类名 类名:operator-()后缀形式:类名 类名:operator-(int)例如:point point:operatot-()-x,-y;return*this;point

27、 point:operatot-(int)point temp(x,y);x-,y-;return temp;,调用 前缀表达式形式:-类对象名;后缀表达式形式:类对象名-;例如:point pt;-pt;/前缀表达式 pt-;/后缀表达式 前缀函数形式:对象名.operator-();后缀函数形式:对象名.operator-(0);例如:point pt;pt.operator-();/前缀函数形式 pt.operator-(0);/后缀函数形式,例4-4 在 point 类中对运算符+和-进行重载,其操作是对 point 类的数据成员 x 和 y 分别进行的。注意,如果在程序中没有重载能用

28、于后缀表达式形式的+和-,则在后缀表达式形式调用+和-时,会出现编译警告,而运行时会以前缀表达式的结果替代后缀表达式的操作结果。返回,4.6 调用运算符()和下标运算符 的重载1 调用运算符“()”顾名思义,调用运算符可以用函数调用表达式的形 式使运算符操作所施加的对象完成任何需要的功能 操作,例如,为操作对象的各个数据成员赋值。调 用该运算符的表达式的一般形式为:类对象名(调用参数列表);可以认为调用运算符是一个双目运算符。左操作数:必须是该运算符操作所施加的类对象。右操作数:为操作类对象所需要传递的一组参数,这组参数可以由任意个数、任何类型的 对象组成,当然也可以无参数。,显然,应该使用成

29、员函数重载调用运算符。原型 返回类型 operator()(调用参数列表);其中返回类型可以是任何合法类型。例如:class point int x,y;public:void operator()(int,int);,定义 返回类型 类名:operator()(调用参数列表)例如:void point:operator()(int x,int y)this-x=x;this-y=y;调用 类对象名(调用参数列表);例如:point pt;pt(50,80);,例4-5 是一个使用成员函数重载调用运算符“()”的简单实例,有助于对调用运算符定义形式、使用方法和用途的理解。注意,调用运算符函数的

30、右操作数,调用参数列表中的参数也允许有缺省参数值。,2 下标运算符“”与调用运算符相似,下标运算符可以用下标访问表 达式的形式对运算符操作所施加对象的相关数据成 员进任何需要的访问和操作,例如,读写矩阵类对 象封装的内部矩阵(数组)的某个指定元素。调用 该运算符的表达式的一般形式为:类对象名 下标列表;可以认为下标运算符也是一个双目运算符。左操作数:必须是该运算符操作所施加的类对象。右操作数:为访问类对象所需要传递的一组下标,这组下标可以由任意个数的整型对象组 成,不允许无下标。,显然,应该使用成员函数重载下标运算符。原型 返回类型 operator();其中返回类型可以是除 void 外的任

31、何合法类型。例如:class Matrix double mt10,10;public:double,定义 返回类型 类名:operator(下标列表)例如:double,例4-6 是一个使用成员函数重载下标运算符的简单实例,有助于对下标运算符定义形式、使用方法和用途的理解。通过重载的下标运算符函数可以方便地访问私有数 据成员 divisionTotals 中的每一个元素。重载下标运算符“”时,返回一个 int 的引用,使得 通过重载的“”访问的元素既能够读,也能够写。因此,下标表达式可以出现在赋值语句的左边。,例4-7 是一个用来处理矩阵运算操作的实例。假定有一个实数矩阵,需要对它进行加法、

32、减法和乘法运算(通过重载运算符+、-、*来实现),为此需要通过重载函数调用运算符(),来返回矩阵元素,以便在实现矩阵的加、减、乘运算中使用。注意:矩阵的析构函数 matrix:matrix()if(elems)/判断矩阵是否不为空delete elems;elems=0;返回,4.7 动态内存管理运算符的重载用于动态内存管理的运算符包括 new、delete(用于单个对象的内存分配和释放)和 new、delete(用于对象数组的内存分配和释放)。系统预定义的这两对运算符可以适用于所有类型对象的动态内存管理,但不能确保对所有类型对象的动态内存管理都高效,尤其是对那些占用内存空间小、结构简单的类型

33、对象,使用那些为了适应复杂情况管理的时、空开销是没有必要的,因此大大降低运行效率。解决这一问题的办法就是重载动态内存管理运算符,定义适应类型对象动态创建和撤消的内存管理操作。,重载动态内存管理运算符的方式有两种:全局方式:重载的动态内存管理运算符完全替代了 原有的动态内存管理运算符 new、delete、new、delete。这种方式一般很少使用,除非确实需要修 改原来的通用管理算法或添加所有类型对象创建和 撤消都需要的附加操作。局部方式:重载的运算符函数只对某个特定类对象 的创建和撤消有效,这是重载动态内存管理运算符 的常用方式。显然,将动态内存管理运算符重载函 数定义为成员函数更符合面向对

34、象程序设计原则。,重载 new 和 delete 运算符的一般方法:定义可以存放 n 个被管理类型对象的静态数组,作 为动态分配的内存储备。定义空闲链,将被回收的对象内存链结起来。定义指示变量,用于指示作为内存储备的静态数组 空间是否已经被初次顺序分配完。重载动态内存分配运算符 new,用于顺序从静态数 组中或从空闲链中为动态创建的对象分配空间。重载动态内存回收运算符 delete,用于释放动态创 建的对象,并将该被释放对象的内存空间链结到空 闲链中。,例4-8 定义适用于 point 类对象动态创建和撤消的内存管理运算符 new 和 delete 重载成员函数,并测试使用重载的 new 和

35、delete 动态创建和撤消 point 类对象的情况。注意,此例中重载的 new 和 delete 运算符每次只能为单个 point 类对象分配和释放内存,而如果分配和释放point 类对象数组则需要重载 new 和 delete 运算符。,1 问题分析 分配一个具有足够空间静态数组(数组元素的结构 应满足存放 point 类对象属性和动态分配和释放的需 要。对该数组的分配和释放操作示意如下:,其中:静态数组的元素由存放位置属性 x 和 y 的单元,以及用于存放被释放元素地址的指针单元组成。静态数据成员 used 用于指示从静态数组中已经被 初次顺序分配的元素个数。静态数据成员 freeli

36、st 用于指向被释放回收的元素 链表。元素的动态分配和释放操作分为两种情况:静态数组中的元素未被初次顺序分配完,即 used 的值小于静态数组的元素总数时(元素第一次被分配):,分配:以 used 的值为下标,为用户动态分配 一个元素,并修改 used 的值。释放:将新释放的元素与 freelist 指向的存放 已释放元素的空闲链连接,并使 freelist 指向 修改后的空闲链。数组中的元素已被初次顺序分配完,即 used 的 值大于静态数组的元素总数时(数组中元素的再 分配):分配:如果被释放元素的空闲链不为空,则从 空闲链中动态分配一个元素,并修改空闲链。,释放:将新释放的元素与 fre

37、elist 指向的存放 已释放元素的空闲链连接,并使 freelist 指向 修改后的空闲链。将运算符 new 和 delete 重载函数声明和定义为 point 类的成员函数,point 类的类图被描述如下:,2 详细设计 类设计 point 类 类定义class point int x,y;static int used;static block*freelist;public:point(int vx,int vy);void*operator new(size_t size);void operator delete();void print();,其中,block 为用于动态定义 p

38、oint 对象时,分 配其属性空间的数据结构:struct block int x,y;block*next;算法描述 重载运算符 new 的算法 N-S 图:,res=freelist修改 freelist,res=0,res=&blockusedused=used+1,返回结果指针 res,重载运算符 delete 的算法 N-S 图:类的应用 在主函数 main 中动态创建 point 对象、显示对象 信息;然后动态释放动态创建 point 对象,用以测试重载的动态内存管理运算符 new 和 delete 的功能。返回,被释放元素的指针 next=freelist,freelist=&(

39、被释放元素的指针),4.8 赋值运算符重载类对象之间的赋值操作是在一一对应的数据成员之间进行的。所以赋值运算符的重载函数应作为类的成员函数。赋值运算符成员函数的原型、定义和调用表达式如下:原型 类名 operator=(const 类名,定义 类名 类名:operator=(const 类名,调用 对象名1=对象名2;例如:point pt1,pt2;pt2=pt1;系统会为每一个类缺省定义一个隐含的赋值运算符成员函数。通常情况下,缺省赋值运算符是可以胜任类对象之间的赋值操作,但在某些特殊情况下,如类中有指针类型的数据成员时,使用缺省赋值运算符就可能产生错误。例如:,#include#incl

40、ude class stringchar*ptr;public:string(char*s)ptr=new charstrlen(s)+1;strcpy(ptr,s);string()delete ptr;void print()cout ptr endl;,void main()string p1(Chen);string p2(“Zhang);p2=p1;cout p2:;p2.print();cout p1:;p1.print();,在执行 p1.print();时将发生错误。原因是执行赋值语句 p2=p1,使 p2.ptr 和 p1.ptr 都指向了“Chen”占用的内存空间,而 p2

41、.ptr 原来所指向的“Zhang”占用的内存空间被泄漏。当 p2 的生命周期结束时,系统自动调用析构函数将这一内存空间撤消。这时 p1 的指针成员 ptr 所指向的内存空间已经成为不允许访问的非法空间了。下图描述了这一错误产生的过程:,执行 p2=p1 之前,解决这一问题的方法是显式地定义一个重载赋值运算符的成员函数,使 p1 和 p2 有各自的存储空间。string/返回被赋值的类对象引用,两点说明:重载类的赋值运算符应该使用成员函数,而不应使 用友元函数,如果上述赋值运算符重载使用了友元 函数:friend string 这时,表达式p1=chen 将被解释为operator=(p1,c

42、hen)这显然是没有什麽问题的,但对于表达式chen=p1/错误的赋值语句 将被解释为:operator=(chen,p1),即 C+编译器首先将“chen”转换成一个隐含的 string 对象,然后引用该隐含对象。因此并不认为这个表 达式是错误的,从而将导致赋值语法的混乱。类的赋值运算符可以被重载,但重载的赋值运算符 成员函数在其派生类中是不能被继承的。建议:在属性的复制操作上,重载赋值运算符和拷贝构造函数具有一致性。因此,在需要用户自定义重载赋值运算符和拷贝构造函数的情况下,一般先定义一个实现属性复制操作的私有成员函数,然后在重载赋值运算符和拷贝构造函数定义中调用此私有成员函数。这样既保证

43、了重载赋值运算符和拷贝构造函数的一致性,又提高了代码的重用。返回,4.9 输入输出运算符重载4.9.1 重载输入运算符 输入运算符“”是输入流类(将在第八章中详细讲述)的成员函数。该运算符也是一个双目运算符,它的左操作数必须是输入流类对象的引用,表示被输入的信息必须来自标准的输入流设备;而右操作数是接收输入信息的指定类对象的引用。因此,为自定义类重载的输入运算符函数只能是类的友元函数。,1 原型 friend 输入流类,2 定义 输入流,3 调用 cin 类对象名;例如:point pt;cin pt;,注意:输入运算符重载函数的第一个参数的类型必须是输 入流类 istream 对象的引用,形

44、参名(流对象名)可 以使用任何合法的标识符。输入运算符重载函数的第二个参数的类型必须是接 收输入信息的指定类对象的引用,例如 point&,而 不能使用指定类名,例如 point。输入运算符重载函数的返回类型必须是输入流类 istream 对象的引用,并且在函数体中由 return 返回 的输入流类对象的引用名必须与第一个参数的形参 名(流对象名)一致。,4.9.2 重载输出运算符“输出运算符“”是输出流类(将在第八章中详细讲述)的成员函数。该运算符也是一个双目运算符,它的左操作数必须是输出流类对象的引用,表示信息必须被输出到标准的输出流设备;而右操作数是输出信息的指定类对象。因此,为自定义类

45、重载的输入运算符函数也只能是类的友元函数。,1 原型 friend 输出流类,2 定义 输出流,3 调用 cout pt;cout pt;,注意:输出运算符重载函数的第一个参数的类型必须是输 出流类 ostream 对象的引用,形参名(流对象名)可 以使用任何合法的标识符。输出运算符重载函数的第二个参数的类型必须是输 出信息的指定类对象或对象的引用,例如 point&,或 point。输出运算符重载函数的返回类型必须是输出流类 ostream 对象的引用,并且在函数体中由 return 返回 的输出流类对象的引用名必须与第一个参数的形参 名(流对象名)一致。,例8-3 中对输入运算符 和输出运

46、算符 和输出运算符 进行了重载,使它们能对按照指定形式(例如由分子分母组成的分数形式,3/8)表示的有理数进行标准输入输出操作。返回,4.10 类型转换(函数)所谓类型转换是指编译器将一种数据类型值转换为另一种数据类型值的功能。1 在 C+中,系统预定义类型和用户自定义类型的对 象均可以进行类型转换。2 类型转换有两种形式:隐式类型转换 如果发生了对象类型与表达式的语法要求不符的情况,编译器能按照语言标准确定的规则自动地将对象的类型进行转换。显式类型转换 用户使用特定的语法表示形式(类型转换法形式或函数法形式)指示编译器将对象的当前类型转换为指定类型。,4.12.1 预定义数据类型间的类型转换

47、1 隐式类型转换 如果赋值表达式 A=B 中操作数的类型不一致,则赋值运算符右端 B 会自动转换为 A 的类型后再进行赋值运算。当 char 或 short 类型变量与 int 类型变量进行运算时,char 或 short 类型变量会先自动转换成 int 类型,然后再进行运算。如果参与算术运算的两个操作对象类型不一致,则两个操作对象中精度低的类型会自动转换为精 度高的类型。,2 显式类型转换 强制转换法 格式:(类型名)表达式例如:int i,j;cout(float)(i+j);/将 i+j 的运算结果强制转换成 float 类型后输出 函数法 格式:类型名(表达式)例如:int i,j;c

48、out float(i+j);/将 i+j 的运算结果作为类型函数 float 的参数。两种方法实现的效果是等效的。使用显式类型转换 在有些情况下是必要的,例如:,int a=5,b=8;printf(a=%f b=%f,a,b);会产生不可期望的结果,例如:a=8192.000001 b=0.000000 使用显式转换将上面的输出语句改写为:printf(a=%f b=%f,float(a),float(b);就可以得到可以预期的结果:a=5.000000 b=8.000000,结构类型与预定义类型之间在数据的组成结构上没 有任何共同之处,因此不能进行这两种类型变量的 显式类型转换,但可以进

49、行这两种类型指针变量的 显式类型转换。例如:#include struct example int y;float z;,main()char*str=Windows!;int*p=new int;*p=67;example*ex1,*ex2;ex1=(example*)str;/str 强制转换为 example 指针类型 ex2=(example*)p;/p 强制转换为 example 指针类型 cout y z y z n;,delete p;str=(char*)ex1;/ex1 强制转换为 char 指针类型 cout str n;return 1;输出结果可能如下:26999 4。

50、855453e+3367 0Windows!,4.12.2 通过构造函数进行类型转换 在面向对象程序设计中自定义类型之间以及自定义与预定义数据类型之间的转换是经常发生的。通过类的构造函数进行类型转换就是方法之一,进行这种转换的前提是:如果 A 类对象能够转换为 B 类对象,则类型 B 必须有一个仅以 A 类对象为参数的构造函数。换句话说,就是 B 类对象创建时,只需要传递一个 A 类对象的实参。例如:,class example public:example(int)example(const char*,int i=0);void f(example arg)void main()examp

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 建筑/施工/环境 > 农业报告


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号