C程序设计实践教程下实用教案

上传人:hs****ma 文档编号:568615608 上传时间:2024-07-25 格式:PPT 页数:258 大小:4.27MB
返回 下载 相关 举报
C程序设计实践教程下实用教案_第1页
第1页 / 共258页
C程序设计实践教程下实用教案_第2页
第2页 / 共258页
C程序设计实践教程下实用教案_第3页
第3页 / 共258页
C程序设计实践教程下实用教案_第4页
第4页 / 共258页
C程序设计实践教程下实用教案_第5页
第5页 / 共258页
点击查看更多>>
资源描述

《C程序设计实践教程下实用教案》由会员分享,可在线阅读,更多相关《C程序设计实践教程下实用教案(258页珍藏版)》请在金锄头文库上搜索。

1、11.1 11.1 面向对象的思想(sxing)(sxing)什么是面向过程?什么是面向对象?两种不同的思想、方法。面向过程的程序设计用传统(chuntng)程序设计语言进行程序设计时,必须详细地描述解题的过程。程序设计工作主要围绕设计解题过程来进行,这种传统(chuntng)的程序设计方法称为面向过程的程序设计。第1页/共257页第一页,共258页。面向过程(guchng)程序设计特点 程序处理数据(shj)的一系列过程; 数据(shj)与过程分离; 程序数据(shj)结构算法 缺点重用性差维护困难 第2页/共257页第二页,共258页。面向对象程序设计(chn x sh j)(chn x

2、sh j)面向对象(duxing)程序设计:将面向对象(duxing)方法用于程序设计。出发点与基本原则:模拟人类习惯思维方式,使开发软件的方法尽可能接近人类认识世界解决问题的方法。对象(duxing)作为模块,对象(duxing)是对客观事物的自然的、直接的抽象和模拟,包含了数据及对数据的操作。第3页/共257页第三页,共258页。11.2 11.2 面向对象程序设计的基本(jbn)(jbn)特点封装性将描述对象的数据及处理这些数据的代码集中起来放在对象内部,对象成为(chngwi)独立模块。继承性 从已有类(称为基类)派生出新类。 多态性同一个名字代表不同、但相似的功能。 第4页/共257

3、页第四页,共258页。11.3 11.3 面向对象软件开发的基本(jbn)(jbn)过程软件危机促进了软件工程的形成与发展。软件工程:用系统工程学的原理和方法管理软件开发过程,开发过程分为(fn wi)分析、设计、编程、测试、维护等阶段。面向对象的软件工程分析:明确系统必须做什么。设计:明确软件系统怎么做。实现:选用合适的面向对象编程语言,实现设计阶段描述的软件系统的各个类模块,并根据类的相互关系集成。测试:发现软件中的错误。维护:在软件交付用户使用期间,对软件所作的修改。第5页/共257页第五页,共258页。11.4 11.4 类和对象(duxing)(duxing)在面向对象程序设计中,对

4、象是构成程序的模块,即程序是由一组对象构成的,对象之间通过消息(xio xi)分工协作,共同完成程序的功能。类是面向对象程序设计的核心,是对具有相同属性与行为的一组事物的抽象描述。利用类可以把数据和对数据所做的操作组合成一个整体,实现对数据的封装和隐藏。类是用户自定义的数据类型,是创建对象的模型。 第6页/共257页第六页,共258页。11.4.1 11.4.1 类的定义(dngy) (dngy) 1格式(gshi)关键字class 类名 成员(chngyun)1说明; 成员(chngyun)2说明; 成员(chngyun)n说明; ;类定义体分号不可缺!分号不可缺!第7页/共257页第七页,

5、共258页。2类的组成(zchn)成员数据成员:变量或对象。其类型为:基本类型:intint、floatfloat、doubledouble、charchar、boolbool复合类型:数组、指针、引用、结构、枚举等成员函数成员函数 对数据对数据(shj)(shj)(shj)(shj)成员进行操作。成员进行操作。第8页/共257页第八页,共258页。n n例:例:class Circle private: int radius; public: void setRadius(int r) radius=r; double area( ) return 3.14*radius*radius; ;

6、数据(shj)成员函数(hnsh)成员1?函数(hnsh)成员2第9页/共257页第九页,共258页。在定义一个类时,注意:类只是一种自定义数据类型,类中任何成员数据均不能使用关键字extern、auto或register指定其存储类型,也不能初始化。例如: class Circle int radius=5; /错误 extern float pi; /错误 /省略其它成员 ;成员函数可直接使用类中的任一成员。类类型与结构体类型相似(xin s),结构体类型也可有函数成员,差别在于,类类型的缺省访问权限是private,结构体类型的缺省访问权限是public。第10页/共257页第十页,共2

7、58页。11.4.2 11.4.2 类成员(chngyun)(chngyun)的访问控制 访问权限控制访问权限控制访问权限访问权限private只允许同类的成员函数访问只允许同类的成员函数访问protected允许同类及其派生类的成员函数访问允许同类及其派生类的成员函数访问public允许同一作用域的任何函数访问允许同一作用域的任何函数访问在定义类时,指定其成员访问(fngwn)权限的原则:仅供该类的成员函数访问(fngwn)的成员应指定为私有的。若定义的成员在派生类中也需经常使用,则应指定其为保护的。若允许在类外使用成员时,应将其访问(fngwn)权限定义为公有的。第11页/共257页第十一

8、页,共258页。11.4.3 11.4.3 类的成员函数(hnsh)(hnsh)及重载成员函数必须在类体内(t ni)给出原型说明,至于它的实现,可以放在类体内(t ni),也可以放在类体外。 当成员函数所含代码较少时,一般直接在类中定义该成员函数;当成员函数中所含代码较多时,通常只在类中进行原型的说明,在类外对函数进行定义。第12页/共257页第十二页,共258页。例:class Person class Person char name12; char name12; int age; int age; char sex4; char sex4;public:public: void Pr

9、int( ) void Print( ) coutname,age,sexendl; coutname,age,sexendl; / /省略(shngl)(shngl)其它成员;在类中定义的成员(chngyun)函数第13页/共257页第十三页,共258页。在类外定义成员函数时,函数名应该包含: 类名(li mn)作用域分辩符(:)原函数名 指明该函数(hnsh)(hnsh)是哪个类的成员第14页/共257页第十四页,共258页。class Person char name12; int age; char sex4; public: void Print ( ) ; /省略其它(qt)成员;

10、void Person : Print( ) coutname,age,sex-数据成员名 对象(duxing)(duxing)指针名-成员函数名(参数表) 第19页/共257页第十九页,共258页。访问(fngwn)对象的成员时,要注意成员的访问(fngwn)权限。例: #include class Rectangle float length,width; public: float Area( ) return length*width; float Perimeter( ) return 2*(length+width); ;private:以关键字classclass定义的类,成员的

11、缺省访问(fngw(fngw n)n)属性为privateprivate。第20页/共257页第二十页,共258页。void main( ) Rectangle r; r.width=45; r.length=54.2; coutthe area of rectangle is r.Area()endl; coutthe perimeter of rectangle is r.Perimeter()endl;非法访问 私有(syu)(syu)成员!第21页/共257页第二十一页,共258页。#include#includeclass Rectangleclass Rectangle float

12、 length,width; float length,width;public:public: void void SetWidthSetWidth(float newWidth)(float newWidth) width=newWidth; width=newWidth; void void SetLengthSetLength(float newLength)(float newLength) length=newLength; length=newLength; float Area( ) float Area( ) return length*width; return lengt

13、h*width; float Perimeter( ) float Perimeter( ) return 2*(length+width); return 2*(length+width); ;例:第22页/共257页第二十二页,共258页。void main( )void main( ) Rectangle r; / Rectangle r; /定义(dngy)(dngy)对象 r.SetWidth(45); r.SetWidth(45); r.SetLength(54.2); r.SetLength(54.2); coutThe area of rectangle is coutThe

14、area of rectangle is r.Area( )endl; r.Area( )endl; coutThe perimeter of rectangle is coutThe perimeter of rectangle is r. Perimeter( )endl; r. Perimeter( )endl; 第23页/共257页第二十三页,共258页。11.4.5 11.4.5 对象(duxing)(duxing)数组 若一个数组中每个元素都是同类型(lixng)的对象,则称该数组为对象数组。定义一维对象数组的格式为: ;第24页/共257页第二十四页,共258页。例11.5 对象

15、(duxing)数组的使用。#include class Circle int radius;public: void setRadius(int r) radius=r; int getRadius( ) return radius; double area( ) return 3.14*radius*radius;void main( ) Circle c3; c0.setRadius(6); c1.setRadius(2); c2.setRadius(15); for(int i=0;i3;i+) coutradius:ci.getRadius( ) ,area:ci.area( )th

16、is-thisthis指针具有(jyu)(jyu)如下的缺省说明: Circle *const this; Circle *const this; 第26页/共257页第二十六页,共258页。有时(yush)不得不显式使用this指针。数据成员名与函数成员的形参同名。例: class Name char* name; public: void SetName(char* name) if(name) this-name=new charstrlen(name)+1; strcpy(this-name,name); else this-name=NULL; ;第27页/共257页第二十七页,共2

17、58页。引用(ynyng)对象本身。例: class Name char* name; public: void CopyName(Name& n) if(this=&n) return;/避免对象自我复制 delete name; if(n.name) name=new charstrlen(n.name)+1; strcpy(name,n.name); else name=NULL; ;第28页/共257页第二十八页,共258页。11.5 11.5 构造函数(hnsh)(hnsh)和析构函数(hnsh) (hnsh) 构造函数和析构函数:类的特殊成员。构造函数:创建对象,并初始化对象的数据

18、成员。析构函数:撤销对象,收回它所占内存(ni cn),及完成其它清理工作。第29页/共257页第二十九页,共258页。11.5.1 11.5.1 构造函数 构造函数的定义(dngy)格式 ClassName() /函数体它与所在类同名。它不得(bu de)(bu de)有返回值,甚至连关键字voidvoid也不许有。第30页/共257页第三十页,共258页。 例:class Personclass Person char name12; char name12; int age; int age; char sex4; char sex4;public:public: Person(cons

19、t char*n,int a,const char*s) Person(const char*n,int a,const char*s) strcpy(name,n); strcpy(name,n); age=a; age=a; strcpy(sex,s); strcpy(sex,s); / /省略(shngl)(shngl)其它成员函数;第31页/共257页第三十一页,共258页。例:class Personclass Person char name12; char name12; int age; int age; char sex4; char sex4;public:public:

20、Person(const char* n,int a,const char* s) Person(const char* n,int a,const char* s); / /其它成员函数(hnsh)(hnsh)说明;Person:Person(const char* n,int a,const char* s)Person:Person(const char* n,int a,const char* s) strcpy(name,n); strcpy(name,n); age=a; age=a; strcpy(sex,s); strcpy(sex,s); 也可在类中声明(shngmng)(s

21、hngmng)原型,在类外定义。第32页/共257页第三十二页,共258页。构造函数的特殊性:构造函数在定义类对象时由系统(xtng)自动调用。例:void main( )void main( ) Person p(“ Person p(“张三(zhn sn)”,20, “(zhn sn)”,20, “男” );” ); 当遇此定义语句时,系统(xtng)(xtng)就调用构造函数: Person(const char* n,int a,const char* s) Person(const char* n,int a,const char* s)创建对象p p,并用实参初始化它的数据成员。第

22、33页/共257页第三十三页,共258页。构造函数可以(ky)重载。例: class Point class Point float x,y; float x,y; public: public: Point( )x=0;y=0; /(1) Point( )x=0;y=0; /(1) Point(float a,float b)x=a;y=b; /(2) Point(float a,float b)x=a;y=b; /(2) / / ; ; void main( ) void main( ) Point p1; / Point p1; /匹配(ppi)(1)(ppi)(1) Point p2(

23、2.0,4.2); / Point p2(2.0,4.2); /匹配(ppi)(2)(ppi)(2) 第34页/共257页第三十四页,共258页。构造函数中初始化数据成员(chngyun)的方法在函数(hnsh)体中用赋值等语句进行使用成员初始化列表第35页/共257页第三十五页,共258页。初始化列表(li bio)(li bio)格式: 在形参表和函数体之间,以:号开头,由多个以逗号分隔(fng)的初始化项构成。即 :数据成员名1(初值1),数据成员名n(初值n)举例: Person:Person( ):name(zhang),age(22) strcpy(sex,男); 执行顺序:带有初

24、始化表的构造函数执行时,首先执行初始化表,然后再执行函数体。第36页/共257页第三十六页,共258页。数据(shj)成员的初始化普通数据成员(chngyun)的初始化既可在函数体进行,也可在初始化表进行。例:( (方法(fngf)1)(fngf)1)Point:Point(int x1,int y1)Point:Point(int x1,int y1) x=x1; y=y1; x=x1; y=y1; 或( (方法(fngf)2)(fngf)2)Point:Point(int x1,int y1):x(x1),y(y1) Point:Point(int x1,int y1):x(x1),y(y

25、1) constconst数据成员、数据成员、constconst对象成员和从基类继承的对象成员和从基类继承的数据成员的初始化必须用初始化表。数据成员的初始化必须用初始化表。第37页/共257页第三十七页,共258页。缺省的构造函数C+规定,每个类至少有一个(y )构造函数,否则就不能创建该类的对象。例:class Rectangleclass Rectangle float length,width; float length,width; public: public: float Area( ); float Area( ); float Perimeter( ); float Peri

26、meter( ); ; / /类的实现(shxin)(shxin)void main( )void main( ) Rectangle rect; Rectangle rect; 能否(nn fu)(nn fu)创建RectangleRectangle类的对象?第38页/共257页第三十八页,共258页。类中若未定义构造函数,则C+编译系统自动提供(tgng)一个缺省的构造函数。缺省的构造函数无参,函数体为空,仅创建对象,不作任何初始化工作。class Rectangleclass Rectangle float length,width; float length,width; public

27、: public: float Area( ) ; float Area( ) ; float Perimeter( ); float Perimeter( ); / /类的实现(shxin)(shxin)void main( )void main( ) Rectangle rect; Rectangle rect; 因系统(xtng)(xtng)提供了默认的构造函数: Rectangle( ) Rectangle( ) 第39页/共257页第三十九页,共258页。只要类中定义了一个构造函数,C+编译器就不再提供缺省的构造函数,如还想要无参构造函数,则必须(bx)自己定义。例: class C

28、ircle int radius; public: Circle(int r) radius=r; /(1) ; Circle c1; Circle c2(5);因该类有带一个参数(cnsh)的构造函数,故系统不再提供无参的缺省构造函数!匹配匹配(ppi)(1)(ppi)(1)没有无参构造函数没有无参构造函数第40页/共257页第四十页,共258页。对上例的修改(xigi)方法1:class Circle int radius;public: Circle(int r) radius=r; /(1) Circle( ) /(2);Circle c1(3); Circle c2;匹配匹配(ppi

29、)(1)(ppi)(1)匹配匹配(ppi)(2)(ppi)(2)添加无参构造函数!第41页/共257页第四十一页,共258页。对上例的修改(xigi)方法2 :class Circle int radius;public: Circle(int r=0) radius=r; /(1); Circle c1(3); Circle c2; 匹配匹配(ppi)(1)(ppi)(1)匹配匹配(ppi)(1)(ppi)(1)参数带默认值的构造函数!参数带默认值的构造函数!所有参数都带默认值的构造函数所有参数都带默认值的构造函数也是缺省的构造函数。也是缺省的构造函数。第42页/共257页第四十二页,共25

30、8页。 使用参数带缺省值的构造函数时,应防二义性。如: class Circle int radius; public: Circle() /L1 Circle(int r=0) radius=r; /L2 ; Circle c1; /L3 错误!产生二义性 Circle c2(5); /匹配L2行的构造函数 当编译(biny)L3行的语句时,出错。因Circle类定义了两个缺省的构造函数,编译(biny)器不知道是调用L1行定义的Circle( )还是调用L2行定义的Circle(int r=0)。第43页/共257页第四十三页,共258页。11.5.2 11.5.2 析构函数(hnsh)(

31、hnsh)作用:与构造函数相反,在对象被撤消时由系统自动(zdng)调用,做清理工作。特点:析构函数名必须与类名相同,并在其前面加上字符“”,以便和构造函数名相区别。析构函数没有返回值,函数名前也不能用关键字void。析构函数没有参数,换言之,析构函数是唯一的,析构函数无法重载。第44页/共257页第四十四页,共258页。例11.9 调用(dioyng)析构函数。 #include class Point int x,y; public: Point() x=y=0;coutDefault constructor called.n; Point(int a,int b) x=a;y=b; co

32、utConstructor with parameters called.n; Point( ) coutDestructor called.x,yn; void print( ) coutx,yn; ;为了观察调用构造函数和析构函数的过程,分别在构造函数和析构函数内添加了输出调用信息(xnx)的语句。第45页/共257页第四十五页,共258页。 void main( ) Point p1(5,8),p2; p1.print( ); p2.print( ); Point p2(3,2); p2.print(); coutExit main!n; 程序运行结果(ji gu)(ji gu):Con

33、structor with parameters called.Constructor with parameters called.Default constructor called.Default constructor called.5,85,80,00,0Constructor with parameters called.Constructor with parameters called.3,23,2Destructor called.3,2Destructor called.3,2Exit main!Exit main!Destructor called.0,0Destruct

34、or called.0,0Destructor called.5,8Destructor called.5,8从该程序的输出(shch)结果看:当创建对象时,系统自动调用相匹配的构造函数;当撤销对象时,系统自动调用析构函数。对象撤销的顺序与创建的顺序正好相反。在相同的生存期的情况下,先创建的对象后撤销,后创建的对象先撤销。第46页/共257页第四十六页,共258页。若类的定义中没有显式定义析构函数,则编译器自动为该类产生一个缺省的析构函数。其格式为: ClassName:ClassName( ) 它不执行任何操作。无需显式定义析构函数的典型情况:撤消对象时不做任何清理工作。需要(xyo)显式定

35、义析构函数的典型情况:类中包含指向动态内存的指针数据成员。由于它们所指向的动态内存空间是对象自己管理的,因此在对象即将撤消时,需通过析构函数将所管理的动态内存空间及时释放。第47页/共257页第四十七页,共258页。例11.10 使用析构函数(hnsh),收回动态分配的存储空间。#include#includeclass Person char* name; int age;public: Person(char* n,int a) if(n) name=new charstrlen(n)+1; strcpy(name,n); else name=0; age=a; Person( ) del

36、ete name; void Print() coutname,ageendl; ;void main( ) Person p(“John”,20); p.Print( ); 申请(shnqng)动态内存释放(shfng)动态内存第48页/共257页第四十八页,共258页。对象释放顺序:对象释放的顺序与创建的顺序正好(zhngho)相反。在相同的生存期的情况下,先创建的对象后释放,后创建的对象先释放。第49页/共257页第四十九页,共258页。11.5.3 11.5.3 构造函数的类型转换功能(gngnng)(gngnng)如果类定义中提供了只带一个参数(没有其它参数,或其它参数都有缺省值)的

37、构造函数,则当一个其它类型的数值或变量x赋值给该类的对象A时,由于类型不一致,系统自动以x为实参调用该类的单参数构造函数,创建(chungjin)一个该类的临时对象,并将该临时对象赋值给A,从而实现了隐式类型转换。第50页/共257页第五十页,共258页。例11.11 构造函数完成(wn chng)类型转换功能的例子。#includeclass Money int yuan,jiao,fen;public: Money (double d) int t=d*100; yuan=t/100;jiao=(t-yuan*100)/10;fen=t%10; Money (int y=0,int j=0

38、,int f=0) yuan=y;jiao=j;fen=f; void print( ) coutyuan“元”jiao“角”fen“分n; ;单参数(cnsh)(cnsh)构造函数第51页/共257页第五十一页,共258页。void main( ) Money m1(25,5,7),m2(2.65); m1.print( ); m2.print( ); Money m3=4.57; m3.print( ); m3= 3.29 ; m3.print( );隐式类型转换:调用单参数构造函数,创建(chungjin)Money(chungjin)Money类型的临时对象。并不做隐式类型转换,等价(

39、dngji)(dngji)于: Money m3(4.57); Money m3(4.57);第52页/共257页第五十二页,共258页。11.5.4 11.5.4 拷贝(kobi)(kobi)构造函数拷贝构造函数:特殊的构造函数,其形参是本类的对象的引用,其作用是使用一个已经存在的对象(由拷贝构造函数的参数指定的对象)去初始化一个新的同类的对象。定义(dngy)一个拷贝构造函数的一般形式为: ClassName:ClassName(ClassName &c) . /函数体完成对应数据成员的赋值第53页/共257页第五十三页,共258页。例11.12 使用完成拷贝功能(gngnng)的构造函数

40、。#include#includeclass Point int x,y;public: Point(int a=0,int b=0) x=a;y=b; Point(Point& t) /拷贝构造函数 x=t.x; y=t.y; cout调用了拷贝构造函数!n; int GetX( ) return x; int GetY( ) return y; void Show( ) coutx=x,y=yn; ;第54页/共257页第五十四页,共258页。double Distance(Point p1,Point p2) int dx=p1.GetX()-p2.GetX(); int dy=p1.G

41、etY()-p2.GetY(); return sqrt(dx*dx+dy*dy);Point Mid(Point& p1,Point& p2) int x=(p1.GetX()+p2.GetX()/2; int y=(p1.GetY()+p2.GetY()/2; return Point(x,y);void main( ) Point p0,p1(8,15); Point p2(p1);/调用(dioyng)拷贝构造函数 Point p3=p1; /等价:Point p3(p1); p1.Show(); p2.Show(); p3.Show(); p3=Mid(p0,p3); coutDis

42、tance(p0,p3)endl;函数Distance(Point,Point)Distance(Point,Point)的形参为类的对象,当执行(zhxng)(zhxng)函数调用distance(p0,p3)distance(p0,p3)时,实参初始化形参,两次调用拷贝构造函数,分别用实参对象p0p0和p3p3初始化形参对象p1p1和p2p2。若函数形参是对象的引用,则调用该函数时,不调用拷贝构造函数,如本例的MidMid函数所示。结果(ji gu)(VC6 with (ji gu)(VC6 with sp6)sp6):调用了拷贝构造函数! !调用了拷贝构造函数! !x=8,y=15x=8

43、,y=15x=8,y=15x=8,y=15x=8,y=15x=8,y=15调用了拷贝构造函数! !调用了拷贝构造函数! !8.062268.06226第55页/共257页第五十五页,共258页。默认拷贝构造函数:若定义类时未显式定义该类的拷贝构造函数,则编译系统会在编译该类时自动生成一个默认拷贝构造函数。默认拷贝构造函数的功能是把一个已经存在的对象的每个数据成员的值逐个(zhg)复制到新建立对象对应的数据成员中。例11.12中类Point的拷贝构造函数与默认的拷贝构造函数功能一样,不必显式定义。何时显式定义拷贝构造函数?若类中包含指向动态内存的指针时,用默认的拷贝构造函数初始化对象,将带来严重

44、问题,如例11.13所示。第56页/共257页第五十六页,共258页。例11.13 默认(mrn)拷贝的构造函数。#include#includeclass String char *p;public: String(char*c=0) if(c) p=new charstrlen(c)+1; strcpy(p,c); else p=NULL; String( ) delete p; void Show( ) coutstring=pn; ;void main( )void main( ) String s1(student); String s1(student); String s2(s1

45、);String s2(s1);/A/A s1.Show( ); s1.Show( ); s2.Show( ); s2.Show( ); 第57页/共257页第五十七页,共258页。s1.ps1.ps1s1堆内存(ni (ni cn)cn)s2.ps2.ps2s2执行执行A A行语句:调用默认拷贝构造函数,行语句:调用默认拷贝构造函数,用用s1s1对象初始化对象初始化s2s2对象,使对象,使s2s2对象的成员对象的成员指针指针p p与与s1s1对象的成员指针对象的成员指针p p指向同一片内指向同一片内存。存。主函数结束:先撤消主函数结束:先撤消s2s2,s2s2对象释放其对象释放其所指动态内存

46、;其后撤销所指动态内存;其后撤销s1s1对象,但因对象,但因s1s1对象所指动态内存已被对象所指动态内存已被s2s2对象释放,造成对象释放,造成重复释放同一块动态内存,出现运行错误重复释放同一块动态内存,出现运行错误(cuw)(cuw)。定义拷贝构造函数,避免上述错误定义拷贝构造函数,避免上述错误(cuw)(cuw): String(String&s) String(String&s) if(s.p) if(s.p) p=new charstrlen(s.p)+1; p=new charstrlen(s.p)+1; strcpy(p,s.p); strcpy(p,s.p); else p=0;

47、 else p=0; s st tu ud de en n00t t第58页/共257页第五十八页,共258页。11.5.5 11.5.5 对象(duxing)(duxing)成员与构造函数 对象(duxing)成员:一个类的对象(duxing)可做另一个类的数据成员。例如: class Date int year,month,day; public: Date(int y,int m,int d) year=y;month=m;day=d; ; class Person char name12; char sex4; Date birthday; /对象(duxing)成员 public:

48、Person(char*, char*,int,int,int); ;第59页/共257页第五十九页,共258页。对象成员的初始化通过初始化表进行,如: Person:Person(char*n,char*s,int y,int m,int d) :birthday(y,m,d) strcpy(name,n); strcpy(sex,s); 若未在初始化表中初始化对象成员,则系统自动调用该对象成员的缺省的构造函数来初始化。若类中包含对象成员,则在创建该类的对象时,先调用对象成员的构造函数,初始化相应的对象成员,后执行该类的构造函数,初始化该类的其他(qt)成员。如果一个类包含多个对象成员,对象

49、成员的构造函数的调用顺序由它们在该类中的说明顺序决定,而与它们在初始化表中的顺序无关。第60页/共257页第六十页,共258页。例11.14 初始化对象(duxing)成员。#includeclass Point int x,y;public: Point(int a,int b) x=a; y=b; coutConstructor of class Point is called.n; void Show( ) coutx=x,y=yendl; ;第61页/共257页第六十一页,共258页。class Rectangle Point p; int length,width;public: R

50、ectangle(int l,int w,int a,int b):p(a,b) length=l; width=w; coutConstructor of class Rectangle is called.n; void Show( ) p.Show( ); coutlength=length,width=widthendl; ;void main( ) Rectangle r(5,4,45,55); r.Show( ); 程序运行结果(ji gu)(ji gu):Constructor of class Point is called.Constructor of class Point

51、 is called.Constructor of class Rectangle is called.Constructor of class Rectangle is called.x=45,y=55x=45,y=55length=5,width=4length=5,width=4第62页/共257页第六十二页,共258页。11.6 11.6 友元有时需要在对象外部频繁访问对象私有的或保护的数据成员,若通过调用公有成员函数间接访问,由于参数传递、类型检查等都需占用时间,必然降低程序的运行效率。使用友元可在对象外部直接访问对象私有的和保护的数据成员。友元以类的封装性的有限破坏为代价(diji

52、)来提高程序的运行效率,应谨慎使用。友元分为:友元函数和友元类。第63页/共257页第六十三页,共258页。11.6.1 11.6.1 友元函数(hnsh)(hnsh)友元函数(hnsh)的说明格式: friend FuncName();例11.15 用友元函数(hnsh)的方法计算两点距离。 #include #include class Point int x,y; public: Point(int i=0,int j=0) x=i;y=j; int GetX( ) return x; int GetY( ) return y; friend double distance(Point&

53、,Point&); ;普通(ptng)(ptng)函数distance( distance( ) )声明为PointPoint类的友元函数。第64页/共257页第六十四页,共258页。 double distance(Point& p1,Point& p2) int dx=p1.x-p2.x,dy=p1.y-p2.y; return sqrt(dx*dx+dy*dy); void main( ) Point a(8,15),b(3,7); coutThe distance is:distance(a,b)endl; 程序运行结果(ji gu)(ji gu):The distance is:9.

54、43398The distance is:9.43398友元函数distance( )distance( )可通过对象名直接访问PointPoint类的对象p1p1和p2p2的私有数据(shj)(shj)成员x x和y y,提高了程序的效率。第65页/共257页第六十五页,共258页。友元函数说明:友元函数说明:友元函数应在类中说明,可放在类的私有、公有或保护部分,效友元函数应在类中说明,可放在类的私有、公有或保护部分,效果一样。友元函数体可在类内或类外定义。果一样。友元函数体可在类内或类外定义。友元函数不是类的成员,不带友元函数不是类的成员,不带thisthis指针,因此必须将对象名或对指针

55、,因此必须将对象名或对象的引用作为友元函数的参数,并在函数体中用运算符象的引用作为友元函数的参数,并在函数体中用运算符“.”“.”来访来访问问(fngwn)(fngwn)对象的成员。如上例中的对象的成员。如上例中的p1.xp1.x。友元函数可直接访问友元函数可直接访问(fngwn)(fngwn)相关类中的所有成员。相关类中的所有成员。一个类的成员函数也可作为另一个类的友元函数,例如:一个类的成员函数也可作为另一个类的友元函数,例如: class A class A int f(); int f(); ; ; class B class B / /成员定义成员定义 friend int A:f(

56、);/ friend int A:f();/声明类声明类A A的成员函数的成员函数f( )f( ) ; / ; / 是类是类B B的友元的友元在声明这个(zh ge)(zh ge)友元函数时需要在函数名前面加上它的类名和作用域运算符“:”“:”。第66页/共257页第六十六页,共258页。11.6.2 11.6.2 友元类若声明A类为B类的友元,则A类的所有成员函数都成为B类的友元函数。有时友元类的使用(shyng)也是必要的选择。例11.16 友元类。#include #include class Date int year,month,day;public: Date(int y=0,in

57、t m=0,int d=0) year=y;month=m;day=d; void display() coutyearmonthday; friend class Person;第67页/共257页第六十七页,共258页。class Person char name12; char sex4; Date birthday;public: Person(char*n,int y,int m,int d,char*s) strcpy(name,n); strcpy(sex,s); birthday.year=y; birthday.month=m; birthday.day=d; void di

58、splay( ) coutname:name,sex:sex; cout,birthday:birthday.year. birthday.month.birthday.day静态数据成员(chngyun)名访问静态数据成员(chngyun)时也要注意访问权限。第74页/共257页第七十四页,共258页。例11.17 具有(jyu)静态数据成员的Apple类。 #include#includeclass Appleclass Apple float weight; float weight; static int count;/ static int count;/静态数据成员的引用(ynyn

59、g)(ynyng)性说明 static float total_weight; static float total_weight;public:public: Apple(float=0); Apple(float=0); Apple( ); Apple( ); void Show( ); void Show( ); ; 第75页/共257页第七十五页,共258页。Apple:Apple(float w)Apple:Apple(float w) weight=w; weight=w; count+;count+; total_weight+=weight;total_weight+=weig

60、ht; coutNumber of apples=countn; coutNumber of apples=countn; Apple: Apple( )Apple: Apple( ) count-; total_weight-=weight;count-; total_weight-=weight; coutNumber of apples=countn; coutNumber of apples=countn; void Apple: Show( )void Apple: Show( ) coutweight=weight coutweight=weight ,total_weight=t

61、otal_weightn; ,total_weight=total_weightn; 第76页/共257页第七十六页,共258页。/静态数据成员的定义(dngy)性说明:默认值为0int Apple:count; float Apple:total_weight;void main( ) Apple redone(100); Apple greenone(200); Apple yellowone(250); redone.Show( ); greenone.Show( ); yellowone.Show( );第77页/共257页第七十七页,共258页。11.7.2 11.7.2 静态(jn

62、gti)(jngti)成员函数采用静态函数成员的好处是可以不依赖于任何对象,直接(zhji)访问静态数据成员。静态成员函数的定义格式: static 返回值类型 成员函数名(参数表);静态成员函数的引用格式: :() 或 .()第78页/共257页第七十八页,共258页。例11.18 具有静态数据成员(chngyun)和静态成员(chngyun)函数的Apple类。 #include#includeclass Appleclass Apple float weight; float weight; staticstatic int count; int count; staticstatic

63、float total_weight; float total_weight;public:public: Apple(float=0); Apple(float=0); Apple(); Apple(); void ShowWeight(); void ShowWeight(); staticstatic void ShowCount(); void ShowCount(); staticstatic void ShowTotalWeight(); void ShowTotalWeight(); 第79页/共257页第七十九页,共258页。Apple:Apple(float w)Apple:

64、Apple(float w) weight=w;count+; total_weight+=weight; weight=w;count+; total_weight+=weight; coutNumber of apples=countn; coutNumber of apples=countn; Apple:Apple( )Apple:Apple( ) count-; total_weight-=weight; count-; total_weight-=weight; coutNumber of apples=countn; coutNumber of apples=countn; vo

65、id Apple: ShowWeight( )void Apple: ShowWeight( ) coutweight=weightn; coutweight=weightn; void Apple:ShowCount( )void Apple:ShowCount( ) coutNumber of apples=countn; coutNumber of apples=countn; void Apple:ShowTotalWeight( )void Apple:ShowTotalWeight( ) coutTotal weight of apples= coutTotal weight of

66、 apples= total_weightn; total_weightn; 第80页/共257页第八十页,共258页。int Apple:count;float Apple:total_weight;void main( ) Apple:ShowCount( ); Apple:ShowTotalWeight( ); Apple redone(100),greenone(200),yellowone(250); redone.ShowWeight( ); greenone.ShowWeight( ); yellowone.ShowWeight( ); Apple:ShowCount( ); A

67、pple:ShowTotalWeight( );程序运行结果(ji gu)(ji gu):Number of apples=0Number of apples=0Total weight of apples=0Total weight of apples=0weight=100weight=100weight=200weight=200weight=250weight=250Number of apples=3Number of apples=3Total weight of apples=550Total weight of apples=550第81页/共257页第八十一页,共258页。静

68、态成员函数可直接访问静态数据成员,但不能直接访问非静态数据成员(因其没有this指针)。若要访问非静态数据成员,则可通过形参对象来访问。如: class Myclass int x; static int y; public: static int f(Myclass m); ; int Myclass:y=9; int Myclass:f(Myclass m) coutx+y; /对x的访问是错误(cuw)的 return m.x+y; /正确 第82页/共257页第八十二页,共258页。11.8 const11.8 const对象和成员(chngyun)(chngyun)函数 常成员函数:

69、在参数表后用const修饰。定义格式: () const;特点:常成员函数不能直接改变本对象的数据成员,也不能调用该对象的非const成员函数间接改变本对象的数据成员,否则编译时会产生出错信息。用途:主要用于定义不改变数据成员的成员函数。const对象:用const说明的对象。定义格式: const ();特点:其数据成员在该对象的生存期间不能改变。例如: const Date birthday(1983,7,13);const对象只能(zh nn)调用常成员函数(构造函数和析构函数除外),以保证其数据成员不被修改。第83页/共257页第八十三页,共258页。例11.19 常成员(chngyu

70、n)函数举例。 #include#includeclass Dateclass Date int year,month,day; int year,month,day;public:public: Date(int y,int m,int d) Date(int y,int m,int d) year=y;month=m;day=d; year=y;month=m;day=d; void SetDate(int y,int m,int d) void SetDate(int y,int m,int d) year=y;month=m;day=d; year=y;month=m;day=d; v

71、oid display( ) void display( ) coutyear,month,dayendl; coutyear,month,dayendl; void display( ) void display( ) constconst coutyear,month,dayendl; coutyear,month,dayendl; ;第84页/共257页第八十四页,共258页。void main( ) Date weekend(2005,7,8); weekend.display( ); weekend.SetDate(2005,7,22); weekend.display( ); co

72、nst Date birthday(1983,7,13); birthday.display( );程序运行结果(ji gu)(ji gu):2005,7,82005,7,82005,7,222005,7,221983,7,131983,7,13第85页/共257页第八十五页,共258页。常对象和常成员函数的说明:常成员函数在类外定义时也应带const关键字。例如: void Date:display( ) const coutyear,month,dayendl; 常成员函数不能直接改变本对象的数据成员,也不能调用该对象的非const成员函数间接改变本对象的数据成员。若常成员函数改变数据成员

73、的值,则编译时将出现出错信息(xnx)。const可用于区分函数的重载。如类Date中有声明: void display( ); void display( ) const;常对象只能调用其常成员函数,不能调用其它成员函数。若常对象调用非const成员函数,如: birthday.SetDate(2005,7,22); 则编译时将出现出错信息(xnx)。第86页/共257页第八十六页,共258页。11.9 11.9 应用(yngyng)(yngyng)实例例11.20 用类和对象编程解决实际问题。设有若干学生数据,包括学号、姓名、数学成绩和C+成绩,求每位学生的总分及每门课程的平均分。分析:S

74、tudent类包括学号、姓名等数据成员,以及对这些数据进行操作的函数成员。其中,静态数据成员count、msum和csum,分别存放学生人数、数学和C+课程的总分。链表类StuList用于存放学生数据,可完成链表创建(chungjin)、输出等任务。它声明为类Student的友元,更便于高效操作Student类的数据成员。第87页/共257页第八十七页,共258页。#include#includeclass Student /学生类protected: int no; /学号 char name10; /姓名 float math,cpp; /数学成绩(chngj)、C+成绩(chngj) s

75、tatic int count; /累计总人数 static float msum,csum;/分别累计数学和C+总分 Student* next;public: Student( ) Student(int,char*,float,float,Student* =NULL); float sum( ) const /计算每位学生的总分 return math+cpp; 第88页/共257页第八十八页,共258页。 void show1( ) const /输出每位学生(xu sheng)的基本信息 coutnotnamet mathtcpptsum( )endl; static void s

76、how2( ) /输出每门课程的平均分 if(count) cout(msum/count)t(csum/count)math=math; this-cpp=cpp; this-next=next; count+; msum+=math; csum+=cpp;第89页/共257页第八十九页,共258页。class StuList Student*head;public: StuList( ) head=NULL; StuList( ); void create( ); /创建(chungjin)链表 void print( ) const; /输出链表;StuList:StuList( ) S

77、tudent*p; while(head) p=head; head=head-next; delete p; 第90页/共257页第九十页,共258页。void StuList:create( ) /尾结点插入法创建单向链表 Student*p1,*p2; int no; char name10; float math,cpp; coutno; while(no!=-1) coutnamemathcpp; p1=new Student(no,name,math,cpp); if(!head) head=p1,p2=p1;else p2-next=p1,p2=p1; coutno; 第91页/

78、共257页第九十一页,共258页。void StuList:print( ) const Student* p=head; coutshow1( ); p=p-next; cout每门平均分:t; Student:show2( );int Student:count; /定义静态数据成员并初始化float Student:msum,Student:csum;void main( ) StuList li; li.create( ); li.print( );第92页/共257页第九十二页,共258页。程序的一次运行结果如下: 输入学号,-1表示(biosh)结束:1 输入姓名及数学、C+语言成

79、绩:Tom 90 95 输入学号,-1表示(biosh)结束:2 输入姓名及数学、C+语言成绩:Sun 99 83 输入学号,-1表示(biosh)结束:3 输入姓名及数学、C+语言成绩:Pod 63 53 输入学号,-1表示(biosh)结束:-1 学号 姓名 数学 C+语言 总分 1 Tom 90 95 185 2 Sum 99 83 182 3 Pod 63 53 115 每门平均分: 84 77第93页/共257页第九十三页,共258页。第1212章 运算符重载(zhn zi)(zhn zi)12.1 运算符重载(zhn zi)的基本方法12.2 运算符重载(zhn zi)为成员函数1

80、2.3 运算符重载(zhn zi)为友元函数12.4 几个特殊运算符的重载(zhn zi)12.5 运算符重载(zhn zi)的规则12.6 字符串类作业:69运算符重载:重定义(dngy)(dngy)已有运算符,完成特定操作,即同一运算符根据不同的运算对象完成不同的操作。运算符重载是面向对象程序设计的多态性之一。 C+C+允许运算符重载。第94页/共257页第九十四页,共258页。12.1 12.1 运算符重载(zhn zi)(zhn zi)的基本方法 重载运算符的目的(md):使运算符可以直接操作自定义类型的数据。class Complexclass Complex float real,

81、imag; float real,imag; public:public: Complex(float r=0,float i=0)Complex(float r=0,float i=0) real=r;imag=i; real=r;imag=i; Complex Complex addadd( (const Complex &c)const Complex &c) return Complex(real+c.real,image+c.image); return Complex(real+c.real,image+c.image); ;void main( )void main( ) com

82、plex c1(3.2,4.6),c2(6.2,8.7),c3; complex c1(3.2,4.6),c2(6.2,8.7),c3; c3=c1.add(c2);c3=c1.add(c2);问:能否简写成 c3=c1+c2; c3=c1+c2;答:运算符虽能直接操作基本数据(shj)(shj)类型的数据(shj)(shj),但对于自定义类型的数据(shj)(shj),若未重载有关运算符,则不能用这些运算符操作自定义类型的数据(shj)(shj)。第95页/共257页第九十五页,共258页。如何(rh)(rh)重载运算符运算符重载函数:为自定义类型添加一个运算符重载函数,简称运算符函数。这样

83、当该运算符操作指定的自定义类型数据时,系统自动调用(dioyng)该函数完成指定操作。运算符函数分为:成员运算符函数友元运算符函数。说明运算符函数的语法格式: 返回值类型 operator (参数表);关键字关键字重载重载(zhn zi)(zhn zi)运算符,如:运算符,如:+ +、- -等等第96页/共257页第九十六页,共258页。举例为Complex类添加一个(y )完成两个Complex类型对象相加的成员运算符函数 Complex operator+(Complex &c) return Complex(real+c.real,image+c.image);设c1和c2为Comple

84、x类型的对象,则当遇到表达式c1+c2时,系统自动调用上面的运算符重载函数完成加法操作。第97页/共257页第九十七页,共258页。12.2 12.2 运算符重载(zhn zi)(zhn zi)为成员函数定义(dngy)格式: class X public: operator (); ;说明:函数应说明为公有的,否则外界无法使用。系统约定,当函数为类成员时,其第一操作数为当前对象。因此,若为一元运算符,则参数表为空;若为二元运算符,则参数表的参数为第二操作数。成员运算符函数的定义(dngy)方法与普通成员函数相同。第98页/共257页第九十八页,共258页。例12.1 定义(dngy)复数类,

85、重载加(+)、减(-)、乘(*)、除(/)运算符,实现复数的四则运算;重载取负(-)运算符,实现复数的取负操作。#includeclass Complex /复数类 double real,image;public: Complex(double r=0,double i=0)real=r;image=i; void print( ); Complex operator+(Complex&);/重载+:复数+复数 Complex operator-(Complex&);/重载-:复数-复数 Complex operator*(Complex&);/重载*:复数*复数 Complex opera

86、tor/(Complex&);/重载/:复数/复数 Complex operator-( ); /重载-:-复数;第99页/共257页第九十九页,共258页。void Complex:print( ) if(image0) coutrealimage0) coutreal+imagein; else coutrealendl;Complex Complex:operator+(Complex &c) return Complex(real+c.real, image+c.image); Complex Complex:operator-(Complex &c) return Complex(r

87、eal-c.real,image-c.image); Complex Complex:operator*(Complex &c) Complex temp; temp.real=real*c.real-image*c.image; temp.image=real*c.image+image*c.real; return temp;第一操作数为当前(dngqin)对象第100页/共257页第一百页,共258页。Complex Complex:operator/(Complex &c) Complex temp; double r=c.real*c.real+c.image*c.image; te

88、mp.real=(real*c.real+image*c.image)/r; temp.image=(image*c.real-real*c.image)/r; return temp;Complex Complex:operator-( ) return Complex(-real,-image); void main(void) Complex c1(5,2),c2(3,4),c3; c3= c1+c2; c3.print( ); c3= c1+c2+c3; c3.print( ); c3=c1-c2; c3.print( ); c3=c1*c2; c3.print( ); c3=c1/c

89、2; c3.print( ); c3= -c1; c3.print( );实际通过调用“+”“+”运算符重载函数(hnsh)(hnsh)来完成,即: c1.operator+(c2) c1.operator+(c2)实际通过调用取负运算符重载函数(hnsh)(hnsh)来完成,即: c1.operator-( ) c1.operator-( )+ +、- -、* *和/ /运算符重载函数(hnsh)(hnsh)的返回值类型均为ComplexComplex,使它们可继续参加ComplexComplex类型数据的运算,如c1+c2+c3c1+c2+c3。第101页/共257页第一百零一页,共258

90、页。问题:例12.1将+运算符重载为类的成员函数实现(shxin)了“复数+复数”操作,能否通过类似的方法实现(shxin)“复数+实数”和“实数+复数”操作?答:要实现(shxin)“复数+实数”操作,可在复数类Complex中重载下列+运算符: Complex Complex:operator+(double d) return Complex(real+d, image); 但要实现(shxin)“实数+复数”操作,无法在复数类Complex中通过重载+运算符为类的成员函数来实现(shxin)。原因是,将运算符重载为类的成员函数时,其第一操作数只能是当前对象,而不可能是其它类型的数据。对

91、于“实数+复数”这样的操作只能通过重载+运算符为类的友员函数来实现(shxin)。第102页/共257页第一百零二页,共258页。12.3 12.3 运算符重载(zhn zi)(zhn zi)为友元函数定义格式: class X class X public: public: friend operator(); friend operator(); ; ;说明:在函数原型前加关键字friendfriend。类的友元函数可置于类的任何位置。友元函数无thisthis指针,应在参数表中列出每个操作数。若 为一元运算符,则参数表应有(yn yu)(yn yu)一个参数;若 为二元运算符,则参数表应

92、有(yn yu)(yn yu)两个参数,第一个参数为左操作数,第二个参数为右操作数。第103页/共257页第一百零三页,共258页。例12.2 12.2 重载加(+)(+)、减(-)(-)、乘(*)(*)、除(/)(/)运算符为复数类的友元函数实现(shxin)(shxin)复数的四则运算;重载取负(-)(-)运算符为复数类的友元函数实现(shxin)(shxin)复数的取负操作。#include#includeclass Complexclass Complex double real,image; double real,image;public:public: Complex(doubl

93、e r=0,double i=0)real=r;image=i; Complex(double r=0,double i=0)real=r;image=i; void print( ); void print( ); friend Complex operator+(Complex&,Complex&); friend Complex operator+(Complex&,Complex&); friend Complex operator+(Complex&,double); friend Complex operator+(Complex&,double); friend Complex

94、operator+(double,Complex&); friend Complex operator+(double,Complex&); friend Complex operator-(Complex&,Complex&); friend Complex operator-(Complex&,Complex&); friend Complex operator*(Complex&,Complex&); friend Complex operator*(Complex&,Complex&); friend Complex operator/(Complex&,Complex&); frie

95、nd Complex operator/(Complex&,Complex&); friend Complex operator-(Complex&); friend Complex operator-(Complex&);第104页/共257页第一百零四页,共258页。Complex operator+(Complex&c1,Complex&c2) /Complex operator+(Complex&c1,Complex&c2) /复数(fsh)+(fsh)+复数(fsh)(fsh) return Complex(c1.real+c2.real,c1.image+c2.image); re

96、turn Complex(c1.real+c2.real,c1.image+c2.image);Complex operator+(Complex&c,double d) /Complex operator+(Complex&c,double d) /复数(fsh)+(fsh)+实数 return Complex(c.real+d,c.image); return Complex(c.real+d,c.image); Complex operator+(double d,Complex&c) /Complex operator+(double d,Complex&c) /实数+ +复数(fsh

97、)(fsh) return Complex(c.real+d,c.image); return Complex(c.real+d,c.image); Complex operator-(Complex&c1,Complex&c2) /Complex operator-(Complex&c1,Complex&c2) /复数(fsh)-(fsh)-复数(fsh)(fsh) return Complex(c1.real-c2.real,c1.image-c2.image); return Complex(c1.real-c2.real,c1.image-c2.image);Complex opera

98、tor*(Complex&c1,Complex&c2) /Complex operator*(Complex&c1,Complex&c2) /复数(fsh)*(fsh)*复数(fsh)(fsh) Complex t; Complex t; t.real=c1.real*c2.real-c1.image*c2.image; t.real=c1.real*c2.real-c1.image*c2.image; t.image=c1.real*c2.image+c1.image*c2.real; t.image=c1.real*c2.image+c1.image*c2.real; return t;

99、return t; 第105页/共257页第一百零五页,共258页。Complex operator/(Complex&c1,Complex&c2) /Complex operator/(Complex&c1,Complex&c2) /复数(fsh)/(fsh)/复数(fsh)(fsh) Complex t; Complex t; double r=c2.real*c2.real+c2.image*c2.image; double r=c2.real*c2.real+c2.image*c2.image; t.real=(c1.real*c2.real+c1.image*c2.image)/r;

100、 t.real=(c1.real*c2.real+c1.image*c2.image)/r; t.image=(c1.image*c2.real-c1.real*c2.image)/r; t.image=(c1.image*c2.real-c1.real*c2.image)/r; return t; return t; Complex operator-(Complex&c1) /Complex operator-(Complex&c1) /复数(fsh)(fsh)取负 return Complex(-c1.real,-c1.image); return Complex(-c1.real,-c

101、1.image); void Complex:print( )void Complex:print( ) if(image0) coutrealimagein; if(image0) coutrealimage0) coutreal+image0) coutreal+imagein; else coutrealendl; else coutrealendl; 第106页/共257页第一百零六页,共258页。void main(void)void main(void) Complex c1(5,2),c2(3,4),c3; Complex c1(5,2),c2(3,4),c3; c3= c1+c

102、2; c3.print( ); c3= c1+c2; c3.print( ); c3=c1-c2; c3.print( ); c3=c1-c2; c3.print( ); c3=c1*c2; c3.print( ); c3=c1*c2; c3.print( ); c3=c1/c2; c3.print( ); c3=c1/c2; c3.print( ); c3= -c1; c3.print( ); c3= -c1; c3.print( ); c3=c1+8.2; c3.print( ); c3=c1+8.2; c3.print( ); c3=3.5+c2; c3.print( ); c3=3.5

103、+c2; c3.print( ); 从例12.112.1和例12.212.2可见,不管运算符的重载(zhn zi)(zhn zi)是通过成员函数实现还是通过友元函数实现,运算符的用法是相同的,但编译器所做的解释是不同的。实际通过调用(dioyng)“+”(dioyng)“+”运算符重载函数来完成,即: operator+(c1,c2) operator+(c1,c2)实际通过调用取负运算符重载函数(hnsh)(hnsh)来完成,即: operator-(c1) operator-(c1)第107页/共257页第一百零七页,共258页。大多运算符既可重载为成员函数又可重载为友元函数,给编程带来了

104、灵活性。但从类的封装性和成员数据的安全性角度考虑,重载运算符时应优先重载为成员函数,无法用成员函数重载时才考虑用友元函数。举例:对于例12.112.1和例12.212.2来说只有“实数(shsh)+(shsh)+复数”这种情况无法用成员函数重载加(+)(+)运算符,其余情况均可。因此复数类定义成下列形式更好。class Complex /class Complex /复数类 double real,image; double real,image;public:public: Complex(double r=0,double i=0)real=r;image=i; Complex(doubl

105、e r=0,double i=0)real=r;image=i; Complex operator+(Complex&); / Complex operator+(Complex&); /复数+ +复数 Complex operator+(Complex&,double); / Complex operator+(Complex&,double); /复数+ +实数(shsh)(shsh) friend Complex operator+(double,Complex&);/ friend Complex operator+(double,Complex&);/实数(shsh)+(shsh)+

106、复数 ;第108页/共257页第一百零八页,共258页。12.4 12.4 几个(j )(j )特殊运算符的重载12.4.1 赋值运算符重载(zhn zi)12.4.2 +和- -运算符的重载(zhn zi)12.4.3 下标运算符的重载(zhn zi)12.4.4 函数调用运算符的重载(zhn zi)12.4.5 转换函数 第109页/共257页第一百零九页,共258页。12.4.1 12.4.1 赋值运算符重载(zhn (zhn zi)zi)若没有为类重载赋值运算符,则编译系统自动为该类生成一个缺省的赋值运算符,以实现同类对象之间对应数据(shj)成员的逐一赋值。例:ComplexComp

107、lex c1(4,5),c2;c1(4,5),c2; c2 = c1; c2 = c1;c2.real=c1.real;c2.real=c1.real;c2.imag=c1.imag;c2.imag=c1.imag;何时需要(xyo)为类重载赋值运算符:类中含有指向动态内存的指针。第110页/共257页第一百一十页,共258页。例12.3 用缺省的赋值运算符产生(chnshng)的问题。#include#includeclass Person char*name; int age;public: Person(char*n,int a) name=new charstrlen(n)+1; st

108、rcpy(name,n); age=a; Person( ) delete name; ;第111页/共257页第一百一十一页,共258页。namenamep2p2void main( )void main( ) Person p1(Tom,23),p2(John,18); Person p1(Tom,23),p2(John,18); p2=p1; p2=p1; /用缺省的赋值运算符赋值 namenamep1p1堆p2.name=p1.name;p2.age=p1.age;p2.name=p1.name;p2.age=p1.age;存在问题:存在问题:p2p2对象的成员数据对象的成员数据nam

109、ename所指动态内所指动态内存丢失,无法释放存丢失,无法释放(shfng)(shfng)。p1p1和和p2p2对象的成员数据对象的成员数据namename共用同共用同一块动态内存,使两者的数据不独一块动态内存,使两者的数据不独立,产生逻辑错误。立,产生逻辑错误。p1p1对象被撤销时,其成员数据对象被撤销时,其成员数据namename所指动态内存已被所指动态内存已被p2p2对象释放对象释放(shfng)(shfng),导致一个运行错误。,导致一个运行错误。age=23age=23T To om m002121age=age=2323J Jo oh hn n00第112页/共257页第一百一十二

110、页,共258页。解决(jiju)方法:为该类重载赋值运算符。C+语言规定:赋值运算符必须重载为类的成员函数;重载的赋值运算符不能被派生类继承。例12.3定义(dngy)的Person类应重载赋值运算符:Person& Person:operator=(Person&p) if(this=&p) /避免对象自我赋值 return *this; delete name; name=new charstrlen(p.name)+1; strcpy(name,p.name); age=p.age; return *this;第113页/共257页第一百一十三页,共258页。namenamep1p1堆n

111、amenamep2p2T To om m00J Jo oh hn n00age=age=2323age=18age=18T To om m00namenamep1p1堆namenamep2p2T To om m00age=age=2323age=age=2323图. p2=p1. p2=p1前图. p2=p1. p2=p1后第114页/共257页第一百一十四页,共258页。12.4.2 +12.4.2 +和- - -运算符的重载(zhn zi)(zhn zi)“+”和“-”运算符的重载相类,下面(ximian)以“+”为例。“+”有前置和后置之分,为前置运算时,运算符重载函数的格式为:oper

112、ator+();为后置运算时,运算符重载函数的格式为:operator+(int);其中,type是函数返回值的类型;参数int仅作后置标志,并无其它意义,可以给一个变量名,也可以不给出变量名。第115页/共257页第一百一十五页,共258页。例12.4 定义一个类描述时间(shjin),能分别存放时、分、秒。重载“+”运算符,实现时间(shjin)对象的加1秒运算。#includeclass Timeprotected: int hour,minute,second; void AddOne( ) second+; if(second=60) second-=60; minute+; if(

113、minute=60) minute-=60; hour+; if(hour=24) hour-=24; 第116页/共257页第一百一十六页,共258页。public: Time(int h=0,int m=0,int s=0) second=s%60; minute=(m+s/60)%60; hour=(h+(m+s/60)/60)%24; Time operator+( ) /前置(qin zh)+ AddOne( ); return *this; Time operator+(int) /后置+ Time t=*this; AddOne( ); return t; 第117页/共257页

114、第一百一十七页,共258页。 void show( ) couthour:minute:secondendl;void main( ) Time t1(8,56,57),t2(23,59,59),t3; t1.show(); +t1; t1.show(); t1+; t1.show(); t3=+t1; t1.show(); t3.show(); t3=t2+; t2.show(); t3.show();第118页/共257页第一百一十八页,共258页。上例中的“+”运算符重载为成员(chngyun)函数,若改用友元函数重载,可定义为: Time operator+(Time& t) /前置+

115、 t.AddOne( ); return t; 参数类型(lixng)(lixng)应是TimeTime类的引用。若用TimeTime类的对象做形参,则在调用函数时是用实参对象的值初始化形参,在函数中对形参的修改并不能影响到实参对象。第119页/共257页第一百一十九页,共258页。12.4.3 12.4.3 下标(xi bio)(xi bio)运算符的重载在C+中访问数组元素时,系统并不对下标做越界检查,但C+允许用户通过重载下标运算符来实现这种检查或完成更广义的操作。重载下标运算符的格式: ClassName:operator(); 其中是返回值的类型;指定下标值。重载下标运算符应注意:下

116、标运算符只能(zh nn)由类的成员函数来实现。左操作数必须是对象。下标运算符重载函数有且仅有一个参数。第120页/共257页第一百二十页,共258页。例12.5 定义一维数组类,并重载下标运算符实现一维数组下标的(bio de)越界检查。#include#includeclass Array int*p; int size;public: Array(int i=10) p=new inti; size=i; Array( ) delete p; int getSize( ) const return size; int& operator(int index);第121页/共257页第一百

117、二十一页,共258页。int& Array:operator(int index) if(index=0&indexsize) return pindex; else coutn出错(ch cu):下标index越界!n; exit(2); void main( ) Array a(10); for(int i=0;i10;i+) ai=i; for(i=1;i11;i+) coutai ;程序运行结果:1 2 3 4 5 6 7 8 91 2 3 4 5 6 7 8 9出错(ch cu)(ch cu):下标1010出界! !下标越界(yu ji)(yu ji)检查:若不越界(yu (yu j

118、i)ji),则返回数组中相应元素的引用;否则输出出错信息,并终止程序的执行。当要输出a10a10的值时,因下标越界(yu (yu ji)ji)而终止程序的执行。第122页/共257页第一百二十二页,共258页。*12.4.4 *12.4.4 函数调用运算符的重载(zhn zi)(zhn zi)函数名后的括号( )称为函数调用运算符。函数调用运算符也可重载,但只能(zh nn)重载为非静态的成员函数。重载函数调用运算符的格式: ClassName:operator( )();第123页/共257页第一百二十三页,共258页。例12.6 重载(zhn zi)函数调用运算符,计算下列函数的值:f(x

119、,y)=x2+3xy+y2。#includeclass Funpublic: double operator( )(double x,double y) return x*x+3*x*y+y*y; ;void main( ) Fun f1,f2; coutf1(3.5,2.7)endl; coutf2(1.6,9.4)endl;f1.operator( )(3.5,2.7)f1.operator( )(3.5,2.7)f2.operator( )(1.6,9.4)f2.operator( )(1.6,9.4)程序运行结果(ji (ji gu)gu):47.8947.89136.04136.04

120、第124页/共257页第一百二十四页,共258页。将例12.5定义的一维数组类Array的下标运算符重载 (zhn zi)函 数 改 为 函 数 调 用 运 算 符 重 载(zhn zi)函数,实现一维数组下标的越界检查。#include#includeclass Array int*p; int size;public: Array(int i=10) p=new inti; size=i; Array( ) delete p; int getSize( ) const return size; int& operator( )(int index);第125页/共257页第一百二十五页,共

121、258页。int& Array:operator( )(int index) if(index=0&indexsize) return pindex; else coutn出错:下标(xi bio)index越界!n; exit(2); void main( ) Array a(10); for(int i=0;i10;i+) a(i)=i; for(i=1;i11;i+) couta(i) ;下标越界检查:若不越界,则返回数组中相应元素的引用;否则输出出错信息,并终止(zhngzh)(zhngzh)程序的执行。当要输出a(10)a(10)的值时,因下标越界而终止(zhngzh)(zhngzh

122、)程序的执行。思考题:定义二维数组类DArrayDArray,并重载函数调用运算符,实现二维数组下标的越界(yu ji)(yu ji)检查。第126页/共257页第一百二十六页,共258页。12.4.5 12.4.5 转换(zhunhun)(zhunhun)函数 转换函数:类型转换函数的简称。转换函数的作用:将该类的对象转换成type类型的数据。转换函数只能是类中定义的成员函数,格式为: ClassName:operator ( ); 其中,ClassName是类名;type是要转换后的一种数据类型;operator与type一起(yq)构成转换函数名。该函数不能带有参数,也不能指定返回值类型

123、,它的返回值的类型是type。转换函数的操作数是该类的对象。第127页/共257页第一百二十七页,共258页。例12.7 12.7 定义一个人民币类,类中包含存储元、角、分的数据成员。请为该类定义类型转换函数(hnsh)(hnsh),把人民币类的对象转换为等价的实数。#includeclass RMB int yuan,jiao,fen;/分别(fnbi)为元、角、分public: RMB(int y=0,int j=0,int f=0) yuan=y;jiao=j;fen=f; operator float( ) /将元、角、分转换成实数返回 return yuan+jiao/10.0f+f

124、en/100.0f; ;第128页/共257页第一百二十八页,共258页。void main( ) RMB m(25,50,70); cout m , /隐式类型转换 float(m) , /显式类型转换 (float)m endl;/显式类型转换程序运行结果(ji gu)(ji gu):30.7,30.7,30.730.7,30.7,30.7m.operator float( )m.operator float( )第129页/共257页第一百二十九页,共258页。一个类可定义多个转换函数,但使用时必须采用显式类型转换,以明确(mngqu)所调用的转换函数。例如:#includeclass

125、C int x; float y;public: C(int a=0,float b=0) x=a;y=b; operator int( ) return x; operator float( ) return y; ;void main(void) C c(3,4.5); coutx=int(c),y=float(c)。对运算符函数形式的限制 只能重载为成员函数的运算符:=、-、()、。只能重载为友元函数的运算符: 提取运算符()和插入运算符()。不能改变运算符的操作数个数、优先级和结合性。第131页/共257页第一百三十一页,共258页。不能改变运算符对预定义类型数据的操作方式。例如,“+

126、”运算符操作预定义类型的数据时,表示(biosh)对数据做加法,无法通过重载来改变。可见,重载运算符时至少应有一个自定义类型的数据做操作数。重载的运算符应尽可能保持其原有的基本语义。运算符重载的目的,是模仿该运算符操作预定义类型数据时的习惯用法,来操作自定义类型的对象,使算法的表达更自然流畅,使程序更易理解,否则,不如用一个普通函数来实现。例如,对某自定义类型重载了运算符“+”,则它的含义应是“相加”、“连接”等,而不应是“相减”。第132页/共257页第一百三十二页,共258页。12.6 12.6 字符串类 在定义的字符串类中重载“=”:实现字符串的赋值;重载“+”:实现两个字符串的拼接;重

127、载“+=”:实现字符串的附加;重载“”、“=”:实现两个字符串之间的比较;重载“”:访问(fngwn)字符串中的每个字符;重载“int”:计算字符串的长度;重载提取运算符:实现字符串的输入;重载插入运算符:实现字符串的输出。第133页/共257页第一百三十三页,共258页。例12.8 12.8 实现字符串直接(zhji)(zhji)操作的字符串类。#include#include#includeclass Stringprotected: char *ptr;public: String( ) ptr=0; String(const char*s) ptr=new charstrlen(s)+

128、1; strcpy(ptr,s); 第134页/共257页第一百三十四页,共258页。 String(const String&s) if(s.ptr) ptr=new charstrlen(s.ptr)+1; strcpy(ptr,s.ptr); else ptr=0; String( ) if(ptr) delete ptr; String& operator=(const String&); String& operator=(const char*p); String operator+(const String&); String operator+(const char*p); f

129、riend String operator+(const char*p, const String&s);第135页/共257页第一百三十五页,共258页。 String& String:operator+=(const String&); String& String:operator+=(const char*p); int operator(const String&s) const return strcmp(ptr,s.ptr)(const String&s) const return strcmp(ptr,s.ptr)0; int operator=(const String&s)

130、 const return strcmp(ptr,s.ptr)=0; char& operator(int i); operator int( ) const return strlen(ptr); friend istream& operator(istream&,String&); friend ostream& operator0) t.ptr=new charlen+1; strcpy(t.ptr,ptr); strcat(t.ptr,s.ptr); else t.ptr=0; return t;String String:operator+(const char*p) String

131、t(p); t=*this+t; return t;第138页/共257页第一百三十八页,共258页。String operator+(const char*p,const String&s) String t(p); t+=s; return t;String& String:operator+=(const String& s) *this=*this+s; return *this;String& String:operator+=(const char*p) String t(p); *this+=t; return *this; 第139页/共257页第一百三十九页,共258页。ch

132、ar& String:operator(int i) if(i=strlen(ptr) cout(istream& in,String& s) char temp4096; intemp; s.ptr=new charstrlen(temp)+1; strcpy(s.ptr,temp); return in;ostream& operator(ostream& out,String& s) outs.ptr; return out; 第140页/共257页第一百四十页,共258页。void main( ) String s1(study ),s2(C+ ),s3,s4; s10=s10-a+A

133、; s3=s1+s2; couts3endl; s4=s3+programming; couts4endl; s1+=hard; couts1,串长int(s1)endl; s2=s1; if(s1=s2) couts1=s2n; s2=abc; if(s1s2) couts1s2n;程序运行结果(ji gu)(ji gu):Study C+Study C+Study C+ programmingStudy C+ programmingStudy hard,Study hard,串长1010s1=s2s1=s2s1s2s1s2第141页/共257页第一百四十一页,共258页。第1313章 继承

134、(jchng)(jchng)和派生通 过 继 承 (jchng)已 有 类 的 部分或全部成员,创建新类的过程称为派生。继承(jchng)既简化程序设计,显著提高软件的重用性,又使软件维护更容易。13.1 13.1 继承(jchng)(jchng)13.2 13.2 初始化基类成员13.3 13.3 冲突、支配规则和赋值兼容性13.4 13.4 虚基类13.5 13.5 虚函数作业:6 6,8 8,1010,1111第142页/共257页第一百四十二页,共258页。13.1 13.1 继承(jchng)(jchng)在定义类A时,若使用了已有类B,则称类A继承了类B,并称类B为基类或父类,称类

135、A为派生类或子类。一个派生类又可作为另一个类的基类,一个基类可派生出(shn ch)若干个派生类,这样就构成类树或类族。继承分为单一继承:一个派生类仅有一个基类;多重继承:一个派生类有两个或两个以上的基类。第143页/共257页第一百四十三页,共258页。举例:见图13-1,图中的箭头是从派生类指向(zh xin)基类。在校人员类:描述在校人员的共性信息(如姓名、年龄、身高和性别等)。学生类:单一继承“在校人员类”,并增加描述学生的个性信息(如学号、所学专业和课程等)。职工类:单一继承“在校人员类”,并增加描述(mio sh)(mio sh)职工的个性信息( (如工资、工作部门、职工号、所教课

136、程等) )。在职研究生类:多重继承“学生类”和“职工类”。类族:在校人员类、学生类、职工类、在职研究生类。第144页/共257页第一百四十四页,共258页。单一(dny)(dny)继承格式(g shi): class ClassName: BaseClassName /派生类中新增成员,可为空 ;规定基类成员在派生类中的访问权限,取publicpublic、privateprivate和protectedprotected三者之一或缺省。当AccessAccess为publicpublic时,为公有(gngyu)(gngyu)派生;当AccessAccess为privateprivate时,为

137、私有派生;当AccessAccess为protectedprotected时,为保护派生。AccessAccess缺省时,类为privateprivate;结构体为publicpublic。派生类的类名已定义的类名第145页/共257页第一百四十五页,共258页。公有(gngyu)(gngyu)派生基类成员在公有派生类中保持原有访问(fngwn)权限。基类的public成员,在派生类中仍为public成员。基类的private成员,在派生类中仍为private成员。注意,派生类不能直接使用基类中的私有成员。基类的protected成员,在派生类中仍为protected成员,在派生类中可直接访问

138、(fngwn),但在派生类外,不可直接访问(fngwn)。例13.1 公有派生。#includeclass A int x;protected: int y;第146页/共257页第一百四十六页,共258页。public: int z; A(int a,int b,int c) x=a;y=b;z=c; void Setx(int a) x=a; void Sety(int a) y=a; int Getx( ) return x; int Gety( ) return y; void ShowB( ) coutx=xty=ytz=zn;class B:public A /公有(gngyu)继

139、承基类A,派生类B int Length,Width;public: B(int a,int b,int c,int d,int e): A(a,b,c) Length=d;Width=e; 在派生类的构造函数中,初始化基类的成员数据(shj)(shj):用成员初始化列表调用基类的构造函数,详见13.213.2节。第147页/共257页第一百四十七页,共258页。 void Show( ) coutLength=LengthtWidth=Widthn; coutx=Getx( )ty=ytz=zn; int Sum( ) return Getx( )+y+z+Length+Width; ;vo

140、id main(void) B b1(1,2,3,4,5); b1.ShowB( ); b1.Show( ); coutSum=b1.Sum( ); coutny=b1.Gety( ); couttz=b1.zn;在派生类B B内:直接使用基类中的保护成员y y和公有成员z z;但不能直接使用基类的私有成员x x,例如(lr)(lr),若将GetxGetx( )( )改为x x,则会出现编译错误。在派生类B B外:派生类B B的对象b1b1直接使用(shyng)(shyng)基类的公有成员函数ShowB( )ShowB( )、Gety( )Gety( )和基类的公有成员数据z z。问:能否(n

141、n fu)(nn fu)改为b1.yb1.y?第148页/共257页第一百四十八页,共258页。私有(syu)(syu)派生基类中公有成员和保护成员在私有派生类中均变为私有的,在派生类中仍可直接(zhji)访问,但在派生类之外均不可直接(zhji)访问。基类中的私有成员在私有派生类中不可直接(zhji)访问,当然在派生类之外,更不直接(zhji)访问。例13.2 私有派生。#lncludeclass A int x;protected: int y;第149页/共257页第一百四十九页,共258页。public: int z; A(int a,int b,int c) x=a;y=b;z=c;

142、 void Setx(int a) x=a; int Getx( ) return x; int Gety( ) return y; ;class B:private A /私有(syu)继承基类A,派生类B int Length,Width;public: B(int a,int b,int c,int d,int e) : A(a,b,c) Length=d;Width=e; void Show( ) coutLength=LengthtWidth=Widthn;第150页/共257页第一百五十页,共258页。 coutx=Getx( )ty=ytz=zn; int Sum(void) r

143、eturn Getx( )+y+z+Length+Width; ;void main(void) B b1(1,2,3,4,5); b1.Show( ); coutSum=b1.Sum( )n;在私有派生类B B内:基类的公有和保护成员(chngyun)(chngyun)在派生类中均变为私有,但在派生类中仍可直接使用,如y y和z z。在私有派生类B B外:派生类B B的对象b1b1只能直接访问添加的公有成员(chngyun)Show( )(chngyun)Show( )和Sum( )Sum( ),而不可直接访问基类的公有成员(chngyun)Getx( )(chngyun)Getx( )、G

144、ety( )Gety( )和z z。第151页/共257页第一百五十一页,共258页。实际编程中,公有派生用得最普遍,私有派生用得较少,而保护派生极少使用,这里不作介绍。在派生类内外,对继承基类成员(chngyun)的访问权限,如表11-1所示。 表13.1 公有和私有派生派生方式派生方式基类成员基类成员的访问权的访问权限限派生类中对基类派生类中对基类成员的访问权限成员的访问权限派生类外对基类派生类外对基类成员的访问权限成员的访问权限publicpublicpublicpublicpublicpublic可访问可访问publicpublicprotectedprotectedprotected

145、protected不可访问不可访问publicpublicprivateprivate不可访问不可访问不可访问不可访问privateprivatepublicpublicprivateprivate不可访问不可访问privateprivateprotectedprotectedprivateprivate不可访问不可访问privateprivateprivateprivate不可访问不可访问不可访问不可访问可访问(fngwn)第152页/共257页第一百五十二页,共258页。抽象类抽象类:不能定义(dngy)对象而只能做基类派生新类的类。抽象类举例:构造函数的访问权限为protected的类。

146、 若定义(dngy)此类的对象,则无法在对象外调用它的私有构造函数。但用此类做基类产生公有派生类时,在派生类中可调用其基类的保护成员,即在产生派生类的对象时,允许在派生类的构造函数中调用基类的构造函数。析构函数的访问权限为protected的类。 因在撤消该类的对象时,无法在对象外调用析构函数。 但用此类做基类产生公有派生类时,在派生类中可调用其基类的保护成员,即在撤消派生类的对象时,允许在派生类的析构函数中调用基类的析构函数。含有纯虚函数的类。详见13.5节。抽象类的作用:用于建立类的层次结构。详见13.5节。第153页/共257页第一百五十三页,共258页。多重继承(jchng)(jchn

147、g)格式: class 类名: 类名1, 类名n /派生类中新增成员,可为空 ; 其中,派生类“类名”继承了类名1类名n的所有成员,每个基类的类名前的Access用以限定该基类中的成员在派生类中的访问权限(qunxin),其规则与单一继承相同。例13.3 多重继承。#include第154页/共257页第一百五十四页,共258页。class A /描述圆:(x,y)为圆心(yunxn),r为半径 float x,y,r;public: A(float a,float b,float c) x=a;y=b;r=c; float& AccessX( ) return x; float& Acces

148、sY( ) return y; float& AccessR( ) return r; float Area( ) return r*r*3.14159f; ;class B float High;public: B(float a) High=a; float& AccessHigh( ) return High; ;第155页/共257页第一百五十五页,共258页。class C:public A,private B /描述(mio sh)圆柱体 float Volume;/圆柱体的体积public: C(float a,float b,float c,float d):A(a,b,c),

149、B(d) Volume=Area( )*AccessHigh( ); float GetVolume( ) return Volume; ;void main(void) A a(6,8,9); B b=23; C c(1,2,3,4); coutnx=a.AccessX()ty=a.AccessY() nr=a.AccessR()tAREA=a.Area() nHigh=b.AccessHigh() nVolume=c.GetVolume()n;类C C的成员函数不能直接访问类A A和B B的私有(syu)(syu)成员数据,只能通过基类A A和B B的公有成员函数间接访问。派生类C C的构

150、造(guzo)(guzo)函数调用基类A A和基类B B的构造(guzo)(guzo)函数。第156页/共257页第一百五十六页,共258页。13.2 13.2 初始化基类成员(chngyun)(chngyun)派生类的构造函数完成:初始化派生类中的基类成员:调用基类的构造函数初始化派生类中新增成员数据:调用派生类的构造函数派生类的构造函数的格式(g shi): 其中,ClassName是派生类名;B1、Bn是基类构造函数名;a是形参表,a1、an是实参表。 ClassName:ClassName(a) : B1(a1),Bn(an) 初始化成员列表:冒号后列举基类成员的构造函数和对象成员的构

151、造函数,项与项之间用逗号分隔。初始化成员列表用基类名调用基类的构造函数,可省略基类成员的缺省构造函数( (即无参或所有(suyu)(suyu)参数都带缺省值的构造函数) )。函数体初始化派生类中的其他成员数据。第157页/共257页第一百五十七页,共258页。例13.4 输出(shch)派生类中构造函数与析构函数的调用关系。#includeclass B1 int x;public: B1(int a) x=a; cout基类B1的构造函数!n; B1() cout基类B1的析构函数!n; ;class B2 int y;public: B2(int a=20) y=a; cout基类B2的构

152、造函数!n; B2() cout基类B2的析构函数!n; ;第158页/共257页第一百五十八页,共258页。class D : public B1,public B2 int z;public: D(int a,int b) : B1(a),B2(20) z=b; cout派生类D的构造函数(hnsh)!n; D() cout派生类D的析构函数(hnsh)!n;void main(void) D d(100,200); 程序运行结果(ji gu)(ji gu):基类B1B1的构造函数! !基类B2B2的构造函数! !派生类D D的构造函数! !派生类D D的析构函数! !基类B2B2的析构函

153、数! !基类B1B1的析构函数! !基类构造函数的调用顺序与继承(jchng)基类的顺序有关。基类构造函数的调用顺序与其在初始化成员列表中的顺序无关。说明派生类的对象:先调用各基类的构造函数,后执行派生类的构造函数。若某个基类仍是派生类,则这种调用基类构造函数的过程递归进行。撤消派生类的对象:析构函数的调用顺序正好与构造函数的顺序相反。可将B2(20)B2(20)省略或改为B2()B2(),因基类B2B2带默认值为2020的缺省构造函数。第159页/共257页第一百五十九页,共258页。派生类含对象成员:其构造函数的初始化成员列表既要列举(lij)基类成员的构造函数,又要列举(lij)对象成员

154、的构造函数。例13.5 派生类中包含对象成员。#includeclass B1 int x;public: B1(int a) x=a; cout基类B1的构造函数!n; B1() cout基类B1的析构函数!n; ;class B2 int y;public:第160页/共257页第一百六十页,共258页。 B2(int a) y=a; cout基 类 B2的 构 造 函 数(hnsh)!n; B2() cout基类B2的析构函数(hnsh)!n; ;class D:public B1,public B2 int z; B1 b1,b2;public: D(int a,int b):B1(a

155、),B2(20),b1(200),b2(a+b) z=b; cout派生类D的构造函数(hnsh)!n; D() cout派生类D的析构函数(hnsh)!n; ;void main(void) D d(100,200); 对象成员的构造函数的调用(dioyng)顺序与对象成员的说明顺序有关。对象成员的初始化必须使用(shyng)对象名。对象成员的构造函数的调用顺序与其在初始化成员列表中的顺序无关。基类成员的初始化必须使用基类名第161页/共257页第一百六十一页,共258页。由例13.5程序输出结果可见,在创建派生类D的对象d时,先调用基类的构造函数,再调用对象成员(chngyun)的构造函数

156、,最后执行派生类的构造函数。若有多个对象成员(chngyun),则调用这些对象成员(chngyun)的构造函数的顺序取决于它们在派生类中说明的顺序。在派生类的构造函数的初始化成员(chngyun)列表中,对象成员(chngyun)的初始化必须使用对象名,基类成员(chngyun)的初始化必须使用基类名。程序运行结果(ji gu)(ji gu):基类B1B1的构造函数! !基类B2B2的构造函数! !基类B1B1的构造函数! !基类B1B1的构造函数! !派生类D D的构造函数! !派生类D D的析构函数! !基类B1B1的析构函数! !基类B1B1的析构函数! !基类B2B2的析构函数! !基

157、类B1B1的析构函数! !问题:请写出产生输出(shch)结果的基类成员名或对象成员名。第162页/共257页第一百六十二页,共258页。例13.6 定义(dngy)描述学生的类,并测试。 分析:该程序较长,但结构简单。用面向对象的程序设计方法编写较小或较简单的程序,会使程序显得复杂和冗长,但编写较大的程序时,其优越性就会充分体现出来。继承性可重复使用已有类,避免程序代码和数据结构的重复设计和编写,并简化程序的维护。 自学要求:仔细阅读书中程序,画出类的层次结构图;分别为Person类和Student类添加拷贝构造函数和赋值运算符重载函数,并改写主函数,充分测试添加的成员函数。第163页/共2

158、57页第一百六十三页,共258页。13.3 13.3 冲突(chngt)(chngt)、支配规则和赋值兼容性冲突:在多重继承的派生类中使用(shyng)不同基类中的同名成员时出现的二义性。例13.7 冲突。#includeclass Aprotected: int x;public: void Show() coutx=xn; A(int a=0) x=a; ;第164页/共257页第一百六十四页,共258页。class Bprotected: int x;public: void Show() coutx=xn; B(int a=0) x=a; ;class C:public A,publi

159、c Bprotected: int y;public: int& AccessX() return x; int& AccessY() return y; ;void main(void) C c; c.Show(); 用VC6编译时,对成员数据x有如下错误(cuw)和警告:error C2385: C:x is ambiguouswarning C4385: could be the x in base A of class Cwarning C4385: or the x in base B of class C用VC6编译(biny)时,对成员函数Show( )有如下错误和警告:erro

160、r C2385: C:Show is ambiguouswarning C4385: could be the Show in base A of class Cwarning C4385: or the Show in base B of class C第165页/共257页第一百六十五页,共258页。解决冲突的方法:使各基类中的成员名各不相同;将基类的成员数据的访问(fngwn)权限说明为private,并在相应的基类中提供成员函数访问(fngwn)这些成员数据,但并不实用。原因是,在实际编程时,通常将基类成员数据的访问(fngwn)权限定义为protected,以保障类的封装性和便于派生

161、类访问(fngwn)。用类名限定来指明所访问(fngwn)的成员。格式为: 类名:成员名第166页/共257页第一百六十六页,共258页。例13.8 用类名限定(xindng)来指明所访问的成员。#includeclass Aprotected: int x;public: void Show() coutx=xn; A(int a=0) x=a; ;class Bprotected: int x;public: void Show() coutx=xn; B(int a=0) x=a; ;第167页/共257页第一百六十七页,共258页。classC:publicA,publicBprote

162、cted:inty;public:int&AccessAX()returnA:x;int&AccessBX()returnB:x;int&AccessY()returny;voidmain(void)Cc;c.AccessAX()=35;c.AccessBX()=100;c.AccessY()=300;c.A:Show();/调用(dioyng)类A中的成员函数c.B:Show();/调用(dioyng)类B中的成员函数couty=c.AccessY()n;用类名限定(xindng)(xindng)来指明所访问的成员,避免冲突。第168页/共257页第一百六十八页,共258页。例13.9 多重

163、继承(jchng)中的冲突。#includeclass Aprotected: int x;public: void Show() coutx=xn; A(int a=0) x=a; ;class Bprotected: int x;public: void Show() coutx=xn; B(int a=0) x=a; ;第169页/共257页第一百六十九页,共258页。class C:public A,public Bprotected: int y;public: int& AccessAX() return A:x; int& AccessBX() return B:x; int&

164、AccessY() return y; ;class D:public Cprotected: int z;public: int& AccessZ() return z; ;第170页/共257页第一百七十页,共258页。void main(void) D d; d.AccessAX()=35; d.AccessBX()=100; d.AccessY()=300; d. C:A: Show(); d. C:B: Show(); d.AccessZ()=500; couty=d.AccessY()n; coutz=d.AccessZ()n;现象(xinxing):用VC6编译时,指出此处有错误

165、和警告error C2039: A : is not a member of Cerror C2385: D:Show is ambiguouswarning C4385: could be the Show in base A of base C of class Dwarning C4385: or the Show in base B of base C of class D原因:作用域运算符不能连续使用。改法: d.A:Show( );用VC6编译(biny)时,此处亦有上述问题,最简单的解决办法是将该行改为: d.B:Show();第171页/共257页第一百七十一页,共258页。支

166、配(zhpi)(zhpi)规则支配规则:未用类名限定时,派生类定义的成员优先于基类中的同名(tngmng)成员,并不产生冲突。例13.10 支配规则。#includeclass Aprotected: int x;public: void Show() coutx=xn; ;第172页/共257页第一百七十二页,共258页。class C:public Aprotected: int x;public: int& AccessX() return x ; int& AccessAX() return A:x ;void main(void) C c; c.AccessX()=100; c.Ac

167、cessAX()=200; coutC中的x=c.AccessX()n; coutA中的x=c.AccessAX()n;按支配(zhpi)(zhpi)规则,此处访问的是派生类C C中的新增成员x x。用类名限定(xindng)(xindng),访问的是基类A A中的成员x x。第173页/共257页第一百七十三页,共258页。用基类成员还是(hi shi)(hi shi)对象成员?基类成员举例 class A public: float x; ; class B: public A,public A ;C+规定:任一基类在派生类中只能直接继承一次,以避免上述(shngsh)无法避免的冲突。问题

168、:因派生类B B中含有两个直接继承来的成员x x,在使用类B B的对象访问基类的成员x x时会产生无法避免(bmin)(bmin)的冲突。第174页/共257页第一百七十四页,共258页。对象成员举例:若在B类中,确实需要两个类A的成员,则可用类A的两个对象来实现。例如: class B A a1,a2; /或A a2; ;基类成员和对象成员,在功能上是相同的,但在使用上是有区别的:在派生类中可直接使用基类的成员(访问权限允许的话),但要使用对象成员的成员时,必须(bx)在对象名后加上成员运算符“.”和成员名。参见例13.5。第175页/共257页第一百七十五页,共258页。赋值兼容(jin

169、rn)(jin rn)规则赋值兼容规则:规定(gudng)派生类的对象与其基类的对象之间互相赋值的规则。设: class A public: int x; ; class C:public A public: int y; ; C c1,c2; A a,*pa;第176页/共257页第一百七十六页,共258页。 则有以下规则:派生类的对象可赋给基类的对象,如: a=c1; /将c1中从类A继承的部分赋给a不允许将基类的对象赋给派生类对象,如: c2=a; /不允许可将派生类对象的指针赋给基类型的指针变量,如: pa=&c2;派生类对象可以初始化基类型的引用,如: A&ra=c1;注意:在后两种

170、情况下,使用(shyng)基类的指针或引用时,只能访问从相应基类中继承来的成员,而不允许访问其他基类的成员或在派生类中增加的成员。第177页/共257页第一百七十七页,共258页。13.4 13.4 虚基类尽管一个基类在派生类中只能直接继承(jchng)一次,但并没有限制一个基类在派生类中间接继承(jchng)的次数。例13.12 一个基类在派生类中产生两个拷贝。#includeclass Apublic: int x; A(int a=0) x=a; ;class B:public Apublic: int y; B(int a=0,int b=0):A(b) y=a; void PB()

171、coutx=xty=yn; ;第178页/共257页第一百七十八页,共258页。class C:public Apublic: int z; C(int a=0,int b=0):A(b) z=a; void PC() coutx=xtz=zn; ;class D:public B,public Cpublic: int m; D(int a,int b,int d,int e,int f):B(a,b),C(d,e) m=f; void Print(void) PB(); PC(); coutm=mn; ;第179页/共257页第一百七十九页,共258页。void main(void) D

172、d(100,200,300,400,500); d.Print();程序运行结果(ji (ji gu)gu):x=200 y=100x=200 y=100x=400 z=300x=400 z=300m=500m=500注意到:派生类D D包含两份基类A A成员。两份基类A A成员的成员数据x x的值。问题:若希望类D D只包含一份基类A A成员( (实际编程时较常见,如图13-113-1所示) ),则用例13.1213.12来实现(shxin)(shxin)不仅多占内存,且可能产生冲突,难以保证多份拷贝中的数据一致。如何解决?第180页/共257页第一百八十页,共258页。例13.13 派生类

173、包含(bohn)两份基类成员,产生冲突。#includeclass Apublic: int x; A(int a=0) x=a; ;class B:public Apublic: int y; B(int a=0,int b=0):A(b) y=a; ;class C:public Apublic: int z; C(int a=0,int b=0):A(b) z=a; ;第181页/共257页第一百八十一页,共258页。class D:public B,public Cpublic: int m; D(int a,int b,int d,int e,int f):B(a,b),C(d,e)

174、 m=f; void Print(void) cout x tyn; /E cout x tzn; /F coutmt; ;void main(void) D d1(100,200,300,400,500); d1.Print();冲突:此时编译器无法(wf)(wf)确定成员x x是继承于类B B,还是继承于类C C。消除:用类名限定来指明成员x x源自类B B或类C C。即用B:xB:x代替E E行中的x x,用C:xC:x代替F F行中的x x。第182页/共257页第一百八十二页,共258页。虚基类:在定义派生类时,在继承的公共基类的类名前加关键字virtual,使得公共基类在派生类中只

175、有(zhyu)一份拷贝。虚基类的格式: class ClassName:virtual ClassName1 ; 或 class ClassName: virtual ClassName1 ; 例13.13 定义虚基类,使派生类中只有(zhyu)基类的一份拷贝。#includeclass Apublic: int x;第183页/共257页第一百八十三页,共258页。 A(int a=0) x=a; /L1;class B:virtual public Apublic: int y; B(int a,int b):A(b) y=a; void PB() coutx=xty=yn; ;class

176、 C:public virtual Apublic: int z; C(int a,int b):A(b) z=a; void PC() coutx=xtz=zn; ;第184页/共257页第一百八十四页,共258页。class D:public B,public Cpublic: int m; D(int a,int b,int d,int e,int f):B(a,b),C(d,e)/L2 m=f; void Print(void) PB(); PC(); coutm=mn; ;void main(void) D d(100,200,300,400,500); d.Print(); d.x

177、=400; d.Print();第185页/共257页第一百八十五页,共258页。当改变成员x的值时,由基类B和C中的成员函数(hnsh)输出的x的值是相同的。即派生类D的对象d只有基类A的一份拷贝,如图13-3所示。x的初值为何为0?分析:从构造函数(hnsh)的调用过程看,成员x的初值应为400。原因:虚基类A在类D中只有一份拷贝,系统无法确定是由类B的构造函数(hnsh)还是由类C的构造函数(hnsh)来调用类A的构造函数(hnsh)。此时,系统约定,在执行类B和类C的构造函数(hnsh)时都不调用虚基类A的构造函数(hnsh), 而 是 在 类 D的 构 造 函 数(hnsh)中直接调

178、用虚基类A的缺省的构造函数(hnsh),即x的值置为0。程序运行结果(ji gu)(ji gu): x=0 y=100 x=0 y=100 x=0 z=300 x=0 z=300 m=500 m=500 x=400 y=100 x=400 y=100 x=400 z=300 x=400 z=300 m=500 m=500第186页/共257页第一百八十六页,共258页。进一步讨论: 若将L1行改为 A(int a) x=a; /L1 再用VC6编译时,将指出L2行有错:“error C2512: A:A : no appropriate default constructor availabl

179、e”,即指出类A没有合适(hsh)的缺省的构造函数可用;此时可将L2行改为: D(int a,int b,int d,int e,int f) :B(a,b),C(d,e), A(10) /L2 即在类D的构造函数的初始化成员列表中增加调用虚基类A的构造函数,则将类D中的x成员的初值置为10。注意:用虚基类进行多重派生时,若虚基类没有缺省的构造函数,则在每一个(y )(y )派生类的构造函数的初始化成员列表中都应有对虚基类构造函数的调用。如上面的A(10)A(10)。第187页/共257页第一百八十七页,共258页。如果派生类继承了多个基类,基类中有虚基类和非虚基类,那么在创建该派生类的对象时

180、,首先调用虚基类的构造函数,然后调用非虚基类的构造函数,最后调用派生类的构造函数。若虚基类又有多个,则虚基类构造函数的调用顺序取决于它们(t men)继承时的说明顺序。例13.14 虚基类与非虚基类构造函数的调用顺序。#includeclass A int x;public: A(int a=0) x=a; coutcall A(int=0)n;class B int y;第188页/共257页第一百八十八页,共258页。public: B(int a=0) y=a; coutcall B(int=0)n;class C:public B,virtual public A int z;publ

181、ic: C(int a=0,int b=0):B(b+20),A(b) z=a; coutcall C(int=0,int=0)n; ;void main(void) C c(100,200); 程序运行结果(ji (ji gu):gu):call A(int=0)call A(int=0)call B(int=0)call B(int=0)call C(int=0,int=0)call C(int=0,int=0)第189页/共257页第一百八十九页,共258页。13.5 13.5 虚函数(hnsh)(hnsh)多态性:通过调用同名函数来实现不同功能。分为:编译时的多态性:通过函数重载或运算

182、符重载实现。重载的函数根据(gnj)调用时给出的实参类型或个数,在程序编译时就可确定调用哪个函数。运行时的多态性:在程序执行前,根据(gnj)函数名和参数无法确定应该调用哪个函数,必须在程序的执行过程中,根据(gnj)具体的执行情况来动态确定。它通过类的继承关系和虚函数来实现,主要用来建立实用的类层次体系结构、设计通用程序。第190页/共257页第一百九十页,共258页。虚函数的定义(dngy)(dngy)和使用虚函数为类的非静态成员,定义格式为: virtual FuncName(); 其中,virtual指明该成员函数为虚函数。虚函数的特性:继承性。若某类有某个虚函数,则在它的派生类中,该

183、虚函数均保持虚函数特性。可重定义。若某类有某个虚函数,则在它的派生类中还可重定义该虚函数,此时(c sh)不用virtual修饰,仍保持虚函数特性,但为了提高程序的可读性,通常再用virtual修饰。应强调,在派生类中重定义虚函数时,必须与基类的同名虚函数的参数个数、参数类型及返回值类型完全一致,否则属重载。第191页/共257页第一百九十一页,共258页。例13.15 虚函数(hnsh)。#includeclass Aprotected: int x;public: A() x=1000; virtual void print() coutx=xt; ;class B:public A in

184、t y;public: B() y=2000; void print() couty=yprint(); pa=&b; pa-print();程序运行结果(ji (ji gu)gu):x=1000 y=2000x=1000 y=2000x=1000 y=2000x=1000 y=2000运行时多态性:将派生类的对象b b的指针赋给基类的指针变量papa,符合赋值兼容性规则。执行(zhxng)“pa-print();”(zhxng)“pa-print();”,因print()print()为基类中的虚函数并在派生类中重定义,此时实际调用的是派生类中重定义的虚函数print()print(),而不

185、是基类中的虚函数print()print()。问:将“pa-print();”改为 “pa-A:print();”,结果(ji gu)如何?编译时多态性:b b是派生类对象,对于b.print()b.print()调用,在编译时,根据对象名和支配规则即可确定所调用的print()print()为派生类B B中重定义的print()print(),与print()print()是否是虚函数无关。第193页/共257页第一百九十三页,共258页。关于运行时多态性与虚函数的说明:使用基类类型的指针变量(或基类类型的引用),使该指针指向派生类的对象(或该引用是派生类的对象的别名),并通过(tnggu)

186、指针(或引用)调用指针(或引用)所指对象的虚函数才能实现运行时的多态性。若派生类中没有重定义基类的虚函数时,当调用这种派生类对象的虚函数时,则调用其基类中的虚函数。不能将构造函数定义为虚函数,但通常把析构函数定义为虚函数,以便通过(tnggu)运行时多态性,正确释放基类及其派生类申请的动态内存。与一般成员函数相比,虚函数调用时的执行速度要慢一些。原因是,为了实现运行时多态性,在每个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现的。第194页/共257页第一百九十四页,共258页。虚函数(hnsh)(hnsh)的特殊性例13.16 成员(chngyun)函数调用虚函数。#in

187、cludeclass Apublic: void f1() coutA:f1t; f2(); virtual void f2() coutA:f2t; f3(); void f3() coutA:f3n; ;class B:public Apublic: void f2() coutB:f2t; f3(); void f3() coutB:f3n; ;void main(void) B b; b.f1(); 第195页/共257页第一百九十五页,共258页。问题:在main()函数中,执行b.f1(),即调用基类A中的f1(),然后在f1()中又调用f2(),此时调用的是类A的f2()还是类B

188、重定义的f2()?分析:类A的f1()的定义可用含this指针的等价形式表示(biosh)为: void A:f1() coutf2(); 此处this指针指向基类对象,其类型为: A* const this; 即this指针是基类类型。此外,f2()是基类A的虚函数又在派生类B中重定义,因此,执行“this-f2();”语句必然引发运行时多态性,即调用的是B:f2(),而不是A:f2()。程序运行结果(ji gu)(ji gu):A:f1 B:f2 B:f3A:f1 B:f2 B:f3第196页/共257页第一百九十六页,共258页。例13.17 在构造函数中调用(dioyng)虚函数。#i

189、ncludeclass Apublic: A() f(); virtual void f() coutA:ft; ;class B:public Apublic: B() f(); void f() coutB:ft; ;void main(void) B b; 程序运行结果(ji (ji gu)gu): A:f B:f A:f B:f注意到,创建派生类的对象时,先调用基类的构造函数初始化基类成员,再调用派生类的构造函数初始化自定义成员。由于(yuy)(yuy)在基类A A的构造函数执行时,派生类B B的构造函数尚未执行,因此,基类A A的构造函数中所调用的虚函数f()f()只能是本类的f f

190、或其基类的f()f(),而不可能是派生类中中的f()f()。继承自继承自A基类的成员基类的成员派生类派生类B自定义的成自定义的成员员b第197页/共257页第一百九十七页,共258页。虚析构函数(hnsh)(hnsh)的必要性例 13.18 虚 析 构 函 数(hnsh)的重要性。 B( ) B( ) buf=new char100; buf=new char100; coutcall B( )n; coutcall B( )n; B( ) B( ) delete buf; delete buf; coutcall B( )n; coutcall B( )n; ;void main( )voi

191、d main( ) A *p=new B; /L2A *p=new B; /L2 delete p; /L3delete p; /L3 #include#includeclass Aclass Apublic:public: A( ) A( ) coutcall A( )n; coutcall A( )n; A( ) /L1A( ) /L1 coutcall A( )n; coutcall A( )n; ;class B:public Aclass B:public A char* buf; char* buf;public:public:第198页/共257页第一百九十八页,共258页。问题

192、:main()函数执行L3行语句时,因p是基类类型指针,故仅调用基类的析构函数A(),而不调用派生类的析构函数B()。这样(zhyng),派生类的动态对象申请分配的100字节动态内存未被释放。程序运行结果(ji gu)(ji gu):call A()call A()call B()call B()call A()call A()如何解决(jiju)(jiju)?有以下三种方法: 1. 1.将L3L3行的语句改为:delete (B*)p;delete (B*)p; 2. 2.或将L2L2行的语句改为:B *p=new B;B *p=new B; 3. 3.或将L1L1行的语句改为:virtua

193、l A()virtual A() 第三种方法将基类A A的析构函数说明为虚函数,则其派生类B B的析构函数自动成为虚函数。这样,当main()main()函数执行L3L3行语句时,因p p指向一个派生类对象,故将调用派生类的析构函数B()B()。从使用的简便性上看,通常把析构函数说明为虚函数。第199页/共257页第一百九十九页,共258页。纯虚函数(hnsh)(hnsh)若类的某些虚函数只能抽象出原型,无法定义其实现,则可定义为纯虚函数,其实现由它的派生类定义。纯虚函数的定义格式: virtual FuncName()=0;定义纯虚函数时,其实现不能在类内同时定义,但可在类外或其派生类中定义

194、。如例13.19。虚函数名赋值为0,与函数体为空不同。含有纯虚函数的类肯定是抽象类,因虚函数没有实现部 分 , 不 能 创 建 对 象 。 但 可 定 义 抽 象 类 类 型(lixng)的指针(或引用),以便用这种基类类型(lixng)的指针变量指向其派生类的对象(或用这种基类类型(lixng)的引用变量关联其派生类的对象)时,调用其派生类重定义的纯虚函数,引发运行时多态性。第200页/共257页第二百页,共258页。#includeclass Aprotected: int x;public: A() x=1000; virtual void print()=0;void A:print(

195、) coutx=xn; class B:public A int y;public: B() y=2000; void print() couty=yprint();pa-A:print();程序运行结果(ji gu)(ji gu):y=2000y=2000x=1000x=1000定义抽象类类型的指针指向其派生类的对象,调用其派生类重定义的纯虚函数,引发(yn f)(yn f)运行时多态性。定义纯虚函数(hnsh)(hnsh):接口统一接口与其实现分离。纯虚函数的实现只能在类外或其派生类中定义,通常为后者。例1 13 3. .1 19 9 纯虚函数第201页/共257页第二百零一页,共258页

196、。综合(zngh)(zngh)应用举例纯虚函数的好处(ho chu):接口与其实现分离,提高了类的抽象层次,便于建立接口统一、功能与时俱进的类体系,如微软的MFC类体系和第14章介绍的I/O流类体系等。例13.20 T是计算 的抽象类;Tf1是T的派生类,计算 。数值积分使用矩形法。第202页/共257页第二百零二页,共258页。#include#includeclass T /定义一个使用矩形法求定积分(jfn)的抽象类protected: float a,b; /分别为积分(jfn)下限和上限 int n; /矩形积分(jfn)的等分数public: T(float a,float b,i

197、nt n) this-a=a; this-b=b; this-n=n; virtual float f(float x)=0;/纯虚函数:求f(x)的值 float Area(void); /求f(x)在a,b上的积分(jfn) void Show(char *str) cout函数f(x)=str在a,b 上的积分(jfn)值=Area()endl; ;第203页/共257页第二百零三页,共258页。float T:Area(void) float s=0.0f,x=a,h=(b-a)/n; for(int i=0;iShow(x-sinx); delete p;程序运行结果:函数(hnsh

198、)f(x)=x-sinx在0,3上的积分值=2.46727第204页/共257页第二百零四页,共258页。例13.21* 建立一个通用单向链表,完成插入一个结点、删除一个结点、查找一个结点操作,并输出链表上的各个结点值。并使结点只含一个整数,测试所建立的通用单向链表。分析:由于单向链表的插入、删除、查找等操作都是相同的,只是结点上包含的数据随不同(b tn)的应用有所不同(b tn),因此,抽象表达结点数据是编写通用单向链表操作程序的关键。第205页/共257页第二百零五页,共258页。下面结合图13-6阐述(chnsh)编程思路:1单向链表结点用结点类Node描述。类Node的成员数据中,包

199、括一个指向结点数据的指针Info和一个指向下一个结点的指针Next。其中,Info是抽象结点数据类Object的指针。2实际结点数据由结点数据类IntObj描述。IntObj是抽象结点数据类Object的派生类,成员数据是一个整数,成员函数完成两个结点的比较,输出结点上的数据等。3单向链表由类List描述。List的成员数据为指向链表的首指针Head,成员函数实现链表的各种操作,如插入、删除一个结点等。由于类List需要频繁访问类Node,为了提高类List访问类Node的效率,将类List说明为类Node的友元。第206页/共257页第二百零六页,共258页。/ex13_21.h#inclu

200、declass Node /结点(ji din)类 Object *Info; /指向结点(ji din)的数据域 Node *Next; /指向下一个结点(ji din)public: Node( ) Info=NULL; Next=NULL; Node(Node &node) /拷贝构造函数 Info=node.Info; Next=node.Next; class Object /class Object /抽象(chuxing)(chuxing)结点数据类:用于派生实际结点数据类public:public: Object( ) / Object( ) /可省略 virtual int

201、IsEqual(Object &)=0;/ virtual int IsEqual(Object &)=0;/结点数据的相等比较 virtual void Show( )=0; / virtual void Show( )=0; /输出结点数据 virtual Object( ) ; / virtual Object( ) ; /虚析构函数;第207页/共257页第二百零七页,共258页。 void FillInfo(Object*obj)Info=obj;/Info指 向数据域 friend class List; /声明友元类;class List /定义单向(dn xin)链表类 Nod

202、e *Head; /链表首指针public: List( ) Head=NULL; List( ) DeleteList( ); void AddNode(Node *); /在链表首加一个结点 Node* DeleteNode(Node*); /删除链表中的一个指定结点 Node* LookUp(Object&); /在链表中查找一个指定结点 void ShowList( ); /输出整条链表上的数据 void DeleteList( ); /删除整条链表;第208页/共257页第二百零八页,共258页。void List:AddNode(Node *node) /在链表首加一个结点 if(

203、Head=NULL) /若为空链表时 Head=node; node-Next=NULL; else node-Next=Head; Head=node; Node* List:DeleteNode(Node *node)/从链表上删除(shnch)指定结点 if(node=Head) Head=Head-Next;/删除(shnch)首结点 else /删除(shnch)中间结点或尾结点 Node *p1,*p2; p1=p2=Head; while(p2!=node&p2-Next) p1=p2; p2=p2-Next; if(p2=node) p1-Next=p2-Next; retur

204、n node;第209页/共257页第二百零九页,共258页。Node* List:LookUp(Object &obj)/从链表上查找一个结点(ji din) Node *p=Head; while(p) if(p-Info-IsEqual(obj) return p; p=p-Next; return NULL;void List:ShowList( ) /输出链表上各结点(ji din)的数据 Node *p=Head; while(p) p-Info-Show( ); p=p-Next; 第210页/共257页第二百一十页,共258页。void List:DeleteList( ) /

205、释 放(shfng)链表上各结点 Node *p; while(Head) p=Head; delete p-Info; Head=p-Next; delete p; 第211页/共257页第二百一十一页,共258页。/ ex13_21.cpp#include ex13_21.hclass IntObj : public Object/由Object派生的实际结点数据(shj)类 int data; /实际结点数据(shj)public: IntObj(int x=0) data=x; void SetData(int x) data=x; int IsEqual(Object& obj)/重

206、定义虚函数 IntObj& t=(IntObj&)obj; return data=t.data; void Show( ) /重定义虚函数 coutdatat; ;第212页/共257页第二百一十二页,共258页。void main(void) IntObj *p; Node *pn,*pt,node; List list; for(int i=0;iFillInfo(p); /填写新结点的数据域 list.AddNode(pn); /将新结点加入链表尾 list.ShowList(); /输出链表上各结点的数据值 coutendl; IntObj da; da.SetData(102);

207、/初始化da对象 pn=list.LookUp(da); /从链表上查找含da对象的结点第213页/共257页第二百一十三页,共258页。 /若找到,则从链表上摘下该结点 if(pn) pt=list.DeleteNode(pn); list.ShowList(); /输出(shch)链表上各结点的数据值 coutendl; if(pn) list.AddNode(pt);/将摘下的结点加入链表首 list.ShowList(); /输出(shch)链表上各结点的数据值 cout:用于从流中提取一个字节序列。插入运算符:用于向流中插入一个字节序列。cin使用提取运算符实现数据的输入,其余三个标

208、准流使用插入运算符实现数据的输出。输入流自动将要输入的字节序列形式的数据变换成计算机内部形式的数据(二进制数或ASCII)后,再赋给 变 量 (binling), 变 换 后 的 格 式 由 变 量(binling)的类型确定。输出流自动将要输出的数据变换成字节序列后,送到输出流中。第220页/共257页第二百二十页,共258页。例14.1 使用流cerr和clog实现数据的输出。#includevoid main(void) float a,b; cerrab; if(b!=0) couta/b=a/bn; else cerr除数为零!n;cout、cerr和clog的用法相同但作用不同。c

209、out的输出可重定向(请参见有关操作系统的介绍)。cerr和clog的输出不可重定向。clog为缓冲流,输出的数据不能及时显示。通常(tngchng)将程序中的提示信息(输入提示和出错提示等)用cerr输出,结果数据用cout输出,而clog很少使用。第221页/共257页第二百二十一页,共258页。流的格式(g shi)(g shi)控制格式化输入/输出仅用于文本流,而二进制流是原样(yun yn)输入输出,不必做格式化转换。“iomanip.h”中预定义了13个格式控制函数,用于控制输入/输出数据的格式,如下表所示。格式控制函数名格式控制函数名 功能功能 用用于于decdec设置为十进制设

210、置为十进制I/O I/O hexhex设置为十六进制设置为十六进制I/O I/O octoct设置为八进制设置为八进制I/O I/O wsws提取空白字符提取空白字符I Iendlendl插入一个换行符插入一个换行符O Oflushflush刷新流刷新流O Oresetioflags(lonresetioflags(long)g)取消指定的标志取消指定的标志I/OI/Osetioflags(long)setioflags(long)设置指定的标志设置指定的标志I/OI/Osetfill(int)setfill(int)设置填充空位的字设置填充空位的字符符O Osetprecision(ints

211、etprecision(int) )设置实数的精度设置实数的精度O Osetw(int)setw(int)设置输出数据的宽设置输出数据的宽度度O Oendsends插入字符串结束标插入字符串结束标志志第222页/共257页第二百二十二页,共258页。例14.2 用格式控制(kngzh)函数指定输出数据的域宽和数制。#include#includevoid main(void) int a=256,b=128; cout setw(8) ab=bn; cout hex ab= dec bn;指定变量a a按十六进制输出,b b按十进制输出。注意,hexhex、decdec、octoct的设置是互

212、斥的,一旦(ydn)(ydn)设置,一直有效,直到下一次设置数制为止。指定输出变量(binling)a(binling)a的域宽为8 8,变量(binling)b(binling)b仍按缺省的域宽输出。注意,setwsetw设置的域宽仅对其后的一次插入有效。第223页/共257页第二百二十三页,共258页。流的错误处理 state是类ios中说明的整型数据成员,用于记录输入/输出操作的状态,它的有关(yugun)二进制位的含义由ios类中说明的公有枚举类型io_state声明的枚举常量来描述。公有枚举类型io_state的声明: enum io_state goodbit =0x00, /输入

213、/输出操作正常 eofbit =0x01, /已到达文件尾 failbit =0x02, /输入/输出操作出错 badbit =0x04, /非法输入/输出操作 hardfail=0x80 /无法恢复的错误 ; 第224页/共257页第二百二十四页,共258页。类ios中读取I/O状态的成员函数: int rdstate(); /读取I/O状态字。 int bad(); /若非法操作,返回非零,否则返回0。 void clear(int=0);/清除流中的错误,参数缺省值为0。 int eof(); /若到文件尾,返回非零,否则返回0。 int fail(); /若I/O错,返回非零,否则返回

214、0。 int good(); /若I/O正常,返回非零,否则返回0。若仅清除输入流状态字中的某位,可用clear()函数。如: cin.clear(cin.rdstate() & ios:badbit); 将输入流中的非法输入/输出操作位置为0。若要将输入流中的非法输入/输出操作位置为1,则为: cin.clear(cin.rdstate() | ios:badbit);在I/O操作后,应检测是否发生I/O错误。若发生I/O错误,则应在处理错误之后(zhhu),清除流中的错误标志,以便进行后续I/O操作。但对I/O错误处理不当可能导致程序不能正常运行。第225页/共257页第二百二十五页,共2

215、58页。例14.3 输入不正确的数据时,导致程序出错(ch cu)。#includevoid main(void) int i,s; couti; s=cin.rdstate(); couts=sn; while(s) cin.clear(); /L couti; s=cin.rdstate(); coutnum=i”运算符实现。基本类型数据的输出由类ostream多次重载“”运算符实现。第227页/共257页第二百二十七页,共258页。基本(jbn)(jbn)类型数据的输入使用cin输入基本类型的数据时应注意:当用户输入的数据送到缓冲区中,仅当输入一行的结束符(Enter键)时,才提取数据;

216、只有把输入缓冲区中的数据提取完后,才开始新的输入。在缺省情况下,空格作为输入数据之间的分格符,在输入的数据之间可以用一个或多个空格分开。但输入字符给字符变量时,空格算字符。输入数据的类型必须与提取数据的变量类型一致,否则会出现错误。若程序中没有(mi yu)相应的出错处理程序,则导致计算结果不正确。在输入数据时,换行符(Enter键)有两个作用:一是,告知系统,输入到缓冲区中的数据可以提取;二是,从输入流中提取数据时,作为空格字符处理。第228页/共257页第二百二十八页,共258页。字符(z f)(z f)和字符(z f)(z f)串的输入输入字符的成员(chngyun)函数: int is

217、tream:get();/提取一个字符,并作为返回值。常用。 istream& istream:get(char&); istream& istream:get(unsigned char&); istream& istream:get(signed char&); 输入字符串的成员(chngyun)函数: istream& istream:get(char*,int,char=n); istream& istream:get(unsigned char*,int,char=n); istream& istream:get(signed char*,int,char=n); 功能:从输入流提取

218、一个字符串(但不提取结束字符),并复制到第一个参数所指存储区。其中,第二个参数为至多提取的字符个数(指定值为n时,至多提取n-1个字符,尾部增加一个字符串结束符0),第三个参数为输入字符串的结束字符(缺省值为换行符)。这三个函数并不常用。提取一个字符,并赋给字符型引用(ynyng)(ynyng)参数第229页/共257页第二百二十九页,共258页。输入字符串的成员函数: istream& istream:getline(char*,int,char=n); istream& istream:getline(unsigned char*,int,char=n); istream& istream

219、:getline(signed char*,int,char=n); 功能:与前三个函数相似,区别在于(ziy)它们从输入流中提取的字符串包括结束字符(若实际提取的字符个数小于第二个参数指定的字符个数),复制到第一个参数所指存储区的字符串不包括结束字符。比较常用。第230页/共257页第二百三十页,共258页。基本类型数据(shj)(shj)的输出标准输出流cout、cerr和clog可输出各种( zhn)基本类型的数据。标准输出流输出整数时,缺省的设置为:数制为十进制、域宽为0、数字右对齐、以空格填充。标准输出流输出实数时,缺省的设置为:精度为六位小数、浮点输出、域宽为0、数字右对齐、以空格

220、填充。当输出的实数的整数部分超过七位或有效数字在小数点右边第四位之后时,则转换为科学计数法输出。输出字符或字符串时,缺省的设置为:域宽为0、字符右对齐、以空格填充。域宽为0的含义是按数据的实际占用的字符位数输出,在输出的数据之间没有空格。第231页/共257页第二百三十一页,共258页。字符(z f)(z f)的输出类ostream中输出字符的成员函数: ostream& ostream:put(char); ostream& ostream:put(unsigned char); ostream& ostream:put(signed char);它们功能(gngnng)相同,仅参数类型略有

221、不同。例如: cout.put(a);类ostream中的成员函数: ostream& ostream:flush( );刷新输出流。例如: cout.flush();对于标准输出流,通常只有cout和clog需要用flush()强制刷新输出流的缓冲区。第232页/共257页第二百三十二页,共258页。14.4 14.4 重载(zhn zi)(zhn zi)提取和插入运算符因标准(biozhn)流只能直接输入/输出基本类型的数据,故自定义类型的数据只有转化成基本类型的数据后才可使用标准(biozhn)流。直接输入/输出类类型的数据,需重载“”运算符。重载提取运算符的格式: friend ist

222、ream& operator( istream& , ClassName& );重载插入运算符的格式: friend ostream& operator”“”运算。左操作数类型右操作数类型,或为ClassNameClassName返回值类型必须是ostream&ostream&,以便连续做“”运算。左操作数类型右操作数类型,或为ClassNameClassName因左操作数非本类ClassNameClassName的对象,故该运算符只能重载为类的友元。第233页/共257页第二百三十三页,共258页。例14.4 重载提取和插入(ch r)运算符,直接输入/输出对象。#includeclass

223、Money int Dollar,Cents;public: friend ostream& operator(istream&,Money&); Money(int m=0,int c=0) Dollar=m;Cents=c; ;ostream& operator(ostream& os,Money& m) os¥m.Dollar元tm.Cents( istream& is,Money& m) ism.Dollarm.Cents; return is; void main(void) Money m; coutm; coutch;不再使用指定文件时,应及时关闭文件,如: infile.clo

224、se();第237页/共257页第二百三十七页,共258页。文件(wnjin)(wnjin)的打开用文件流类的成员(chngyun)函数open打开文件:void ifstream:open( const char* , int=ios:in, int=filebuf:openprot );void ofstream:open( const char* , int=ios:out, int=filebuf:openprot );void fstream:open( const char* , int, int=filebuf:openprot );指定打开(d ki)(d ki)的文件名( (

225、可含盘符和路径) ):如“d:t1.dat”“d:t1.dat”。指定文件的打开方式:输入文件流的缺省值为ios:inios:in:按输入文件方式打开文件。输出文件流的缺省值为ios:outios:out:按输出文件方式打开文件。输入输出文件流没有缺省的打开方式,在打开文件时应指明。指定打开文件的保护方式:缺省值与filebuf:sh_compatfilebuf:sh_compat相同。filebuf:sh_compatfilebuf:sh_compat:共享filebuf:sh_nonefilebuf:sh_none:独享filebuf:sh_readfilebuf:sh_read:读共享f

226、ilebuf:sh_writefilebuf:sh_write:写共享第238页/共257页第二百三十八页,共258页。文件的打开方式:用ios类中定义的公有枚举类型open_mode中的枚举常量来描述。 enum open_mode in=0x01, /输入(shr)(读)方式 out=0x02, /输出(写)方式 ate=0x04, /添加方式,文件指针最初指向文件尾 app=0x08, /追加方式,文件指针始终指向文件尾 trunc=0x10, /截断文件方式 nocreate=0x20, /不建文件方式 noreplace=0x40,/不替换文件方式 binary=0x80 /二进制方

227、式 ;因每种打开方式都用一个二进位表示,故在不矛盾的情况下,可用运算符“|”(二进制按位或)将多种打开方式组合使用。第239页/共257页第二百三十九页,共258页。表表14-2 14-2 文件的打开方式文件的打开方式打开方式打开方式作用作用inin输入输入( (读读) )方式。打开文件后,文件指针指向方式。打开文件后,文件指针指向文件头。文件头。outout输出输出( (写写) )方式。单用时,若文件存在,则清方式。单用时,若文件存在,则清空,否则创建一个空文件。空,否则创建一个空文件。ateate添加输出添加输出( (写写) )方式。打开文件后,文件指针方式。打开文件后,文件指针最初最初指

228、向文件尾,但随后可改变指向。指向文件尾,但随后可改变指向。appapp追加输出追加输出( (写写) )方式。打开文件后,文件指针方式。打开文件后,文件指针始终始终指向文件尾,所有输出写入文件尾。指向文件尾,所有输出写入文件尾。trunctrunc截断输出截断输出( (写写) )方式。若文件存在,则清空。方式。若文件存在,则清空。nocreatenocreate不建文件方式。若文件不存在,则打开文件不建文件方式。若文件不存在,则打开文件失败。常与失败。常与inin或或outout组合使用,但不能与组合使用,但不能与noreplacenoreplace组合使用。组合使用。noreplacnorep

229、lace e不替换文件方式。若文件已存在,则打开文不替换文件方式。若文件已存在,则打开文件失败。常与件失败。常与outout方式组合使用,创建新文件。方式组合使用,创建新文件。binarybinary二进制方式。常与二进制方式。常与inin或或outout方式组合使用。方式组合使用。第240页/共257页第二百四十页,共258页。文件打开方式说明不以binary方式打开的文件,都是文本文件。输入文件仅可用in、nocreate和binary及其组合打开。文件指针从0开始(kish)连续编号(0代表文件头),以字节为单位,用于指示文件的读/写位置,文件每读/写一个字节,文件指针就后移一个字节。文

230、件指针的初值由文件打开方式指定,若文件打开方式含app或ate,则文件指针指向文件尾,否则指向文件头,如:文件打开方式文件打开方式作用作用文件指针初文件指针初值值ios:in|ios:nocreate以读方式打开文本文件。以读方式打开文本文件。若文件不存在,则打开失若文件不存在,则打开失败。败。0(文件头文件头)ios:in|ios:binary |ios:nocreate以读方式打开二进制文件。以读方式打开二进制文件。若文件不存在,则打开失若文件不存在,则打开失败。败。0ios:out以写方式打开文本文件。以写方式打开文本文件。 0ios:out|ios:app|ios:nocreate以追

231、加写方式打开文本文以追加写方式打开文本文件。若文件不存在,则打件。若文件不存在,则打开失败。开失败。文件尾文件尾ios:in|ios:out以读写方式打开文本文件。以读写方式打开文本文件。0第241页/共257页第二百四十一页,共258页。用文件(wnjin)流类的构造函数打开文件(wnjin)文件(wnjin)流类ifstream、ofstream和fstream的构造函数所带参数与各自的成员函数open()所带参数完全相同。因此,在说明这三种文件(wnjin)流类的对象时,通过调用各自的构造函数,也能打开文件(wnjin)。读方式打开文件(wnjin) ifstream f1(file1.

232、dat,ios:in|ios:nocreate);写方式打开文件(wnjin) ofstream f2(file2.txt);读方式打开文件(wnjin) fstream f3(file3.dat,ios:in|ios:nocreate);第242页/共257页第二百四十二页,共258页。文件打开与否的判别文件打开后,应判断打开是否成功。若不成功,则后续的文件读/写操作(cozu)就没有实际意义。举例 ifstream f2; f2.open(file.dat,ios:in|ios:nocreate); if( !f2 ) cout”和插入运算符“”来读写文件。第245页/共257页第二百四十

233、五页,共258页。例14.5 复制源文 件到目标文件(wnjin)中。 #include#includevoid main(void) char ch,f1256,f2256; coutf1; coutf2; ifstream in(f1,ios:in|ios:nocreate); ofstream out(f2); if(!in) coutn不能打开源文件(wnjin):f1; exit(1); if(!out) coutn不 能 打 开 目 标 文 件(wnjin):ch ) outchinch操作的返回值为inin,成为whilewhile语句的条件(tiojin)(tiojin)。因不

234、是基本类型数据,无法直接运算,除非做类型转换。iosios类仅重载了一个类型转换函数: operator void*( ) return fail( ) operator void*( ) return fail( ) ? 0 : this; ? 0 : this; 问:可否用 while(!in.eof() inch; outch; 代替左边的代码?隐式类型转换:(void*)in fail()?0:this(void*)in fail()?0:this或while(in.get(ch) outch;问:本程序为何也能复制任意类型的文件?第246页/共257页第二百四十六页,共258页。例1

235、4.6 设文本文件data.txt中有若干实数,每个实数之间用空格或换行符隔开( ki)。求出文件中的这些实数的平均值。#includevoid main(void) float sum=0,t; int count=0; ifstream in(data.txt,ios:in|ios:nocreate); if(!in) coutt) /依次读一个实数 sum+=t,count+; coutn平均值=sum/count ,count=count;设data.txtdata.txt文件的内容(nirng)(nirng)为: 24 56.9 33.7 45.6 24 56.9 33.7 45.6

236、 88 99.8 20 50 88 99.8 20 50程序运行结果: 平均值=52.25,count=8=52.25,count=8第247页/共257页第二百四十七页,共258页。二进制文件(wnjin)(wnjin)的使用用二进制文件方式读写数据(shj)时,数据(shj)不做变换,直接传送。二进制文件的读写操作:使用读写字符的文件流的成员函数(如例14.5);使用文件流的成员函数read()和write()。成员函数read()的原型: istream& istream:read(char*t,int n); istream& istream:read(unsigned char*t,

237、int n); istream& istream:read(signed char*t,int n); 功能:从二进制文件中读取n个字节数据(shj)到t指针所指内存。成员函数write()的原型: ostream& ostream:write(const char*t,int n); ostream& ostream:write(const unsigned char*t,int n); ostream& ostream:write(const signed char*t,int n); 功能:将t所指内存的前n字节数据(shj)写入二进制文件。第248页/共257页第二百四十八页,共258

238、页。例14.7 将150之间的所有偶数存入(cn r)二进制文件data.dat中。#include#includevoid main(void) ofstream out(data.dat,ios:out|ios:binary); if(!out) cout不能打开data.datn; exit(1); for(int i=2;i50;i+=2) out.write( (char*)&i ,sizeof(int); out.close();将整型指针转换成字符型指针,以符合该函数第一个参数(cnsh)(cnsh)类型的要求。第249页/共257页第二百四十九页,共258页。例14.8 14.

239、8 从例14.714.7产生的数据(shj)(shj)文件data.datdata.dat中读取二进制数据(shj)(shj),并按每行1010个数的形式显示。#includevoid main(void) int i,a; ifstream in(data.dat,ios:in|ios:binary|ios:nocreate); if(!in) cout不 能 打 开 (d ki)文 件data.datn; return; for(i=1; in.read(char*)&a,sizeof(int) ;i+) coutat; if(i%10=0) coutn; coutn;函数调用分析(fnx

240、):读sizeof(int)个字节数据送a;返回的文件流对象in做for语句的表达式2,无法直接运算,除非转换转换成基本类型的数据。对象in的唯一类型转换函数“void*”将in隐式转换成基本类型的数据做为for语句的表达式2参加运算。见例14.5。作用:读4 4个字节数据送a a,若输入出错( (包括文件结束) )则返回零,否则为非零。第250页/共257页第二百五十页,共258页。文件(wnjin)(wnjin)的随机读写顺序(shnx)读写:按先后顺序(shnx)读/写文件中的数据,如前所述。随机读写:从文件的任何指定位置读/写数据,主要用于二进制文件。随机读写的前提是移动文件指针。移动

241、输入流文件指针的成员函数: istream& seekg( streampos pos ); /参考点为文件头 istream& seekg( streamoff off, ios:seek_dir dir );streamposstreampos和streamoffstreamoff与longlong同型。pospos和offoff指定(zhdng)(zhdng)文件指针相对于参考点的移动值。若为负值,则前移,否则后移。文件头为前,文件尾为后。seek_dirseek_dir是iosios类中定义的公有枚举类型: enum seek_dir /enum seek_dir /参考点 beg=0

242、 , / beg=0 , /文件头 cur=1, /cur=1, /当前位置 end=2 /end=2 /文件尾 ;dirdir指定文件指针移动的参考点。第251页/共257页第二百五十一页,共258页。文件指针移动举例:设有二进制输入文件流对象f f.seekg(-50,ios:cur); /文件指针从当前位置前移50个字节 f.seekg(50,ios:cur); /文件指针从当前位置后移50个字节 f.seekg(-50,ios:end);/文件指针从文件尾前移50个字节 注意:在移动文件指针时,必须保证移动后的指针值大于等于0且小于等于文件尾字节编号,否则将导致后续读/写出错。移动输出

243、流文件指针的成员函数: ostream& seekp(streampos pos); ostream& seekp(streamoff off,ios:seek_dir dir);获取文件指针值的成员函数: streampos istream:tellg(); /输入流 streampos ostream:tellp(); /输出流 功能(gngnng):返回当前文件指针值(相对于文件头)。随机文件的读写分两步:先将文件指针移到开始读写的位置;再用文件读写函数读或写数据。第252页/共257页第二百五十二页,共258页。例 14.9 将 5 100之 间 的 奇 数 存 入 二 进 制 文 件

244、(wnjin),然后再将文件(wnjin)中的第2029之间的数依次读出并输出。#includevoid main(void) int i,x; ofstream out(d.dat,ios:out|ios:binary); if(!out) cout不能打开文件(wnjin)d.datn; return; for(i=5;i100;i+=2) out.write(char*)&i,sizeof(int); out.close(); ifstream f(d.dat,ios:in|ios:binary); if(!f) cout不能打开文件(wnjin)d.datn; return; f.se

245、ekg(20*sizeof(int);/文 件 (wnjin)指针移到指定位置 for(i=0;i10 & f.read(char*)&x,sizeof(int);i+) coutxt;第253页/共257页第二百五十三页,共258页。14.6 14.6 综合应用(yngyng)(yngyng)举例例14.10 下列程序显示并保存2,1000内的所有素数。显示、保存素数的格式为:每行10个素数,每一素数占7个字符位置,右对齐。最后一行不足10个素数时按一行输出。#include #include #include typedef unsigned long UL;class CPrime/定义

246、求解start,end内所有素数的类protected: UL start,end; /endstart1 int IsPrime(UL i);/若 i 是素数则返回1,否则返回0public: CPrime(UL s=2ul,UL e=1000ul); UL& Start( ); /设置(shzh)/取得start第254页/共257页第二百五十四页,共258页。 UL& End( ); /设置/取得end /向 os流 输 出 p对 象 在 start,end内 的 所 有(suyu)素数 friend ostream& operator(ostream& os,CPrime& p);in

247、t CPrime:IsPrime(UL i) UL j,k=UL(sqrt(i); for(j=2;jk) return 1; else return 0;CPrime:CPrime(UL s,UL e) start=s; end=e; UL& CPrime:Start( ) return start; UL& CPrime:End( ) return end; 第255页/共257页第二百五十五页,共258页。ostream& operator(ostream& os,CPrime& p) UL i; int count; for(i=p.start,count=0;ip.end;i+) i

248、f(p.IsPrime(i) if(count=10) osendlsetw(7)i;count=0; else ossetw(7)i; count+; osendl; return os;void main(void) CPrime prime; ofstream f(prime.txt); coutprime;/显示2,1000内的所有素数 fnext=next。重载取负(-)运算符,实现复数的取负操作。C+规定:任一基类在派生类中只能直接继承一次,以避免上述无法避免的冲突。接口统一接口与其实现分离。void FillInfo(Object*obj)Info=obj。最后一行不足10个素数时按一行输出。感谢您的欣赏第二百五十八页,共258页。

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 高等教育 > 研究生课件

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号