C面向对象程序设计.ppt

上传人:tia****nde 文档编号:12706234 上传时间:2020-05-14 格式:PPT 页数:153 大小:1.06MB
返回 下载 相关 举报
C面向对象程序设计.ppt_第1页
第1页 / 共153页
C面向对象程序设计.ppt_第2页
第2页 / 共153页
C面向对象程序设计.ppt_第3页
第3页 / 共153页
点击查看更多>>
资源描述
第3章C+面向对象程序设计,与传统的面向过程的程序设计(POP,ProcedureOrientedProgramming)语言相比,C+语言的最大特征是支持面向对象程序设计OOP(ObjectOrientedProgramming),它引入了类、继承、多态和重载等面向对象的新机制。通过本章的学习,使我们系统地介绍C+面向对象设计的基本概念和基本方法。,3.1面向对象程序设计方法及特征,面向对象不只是一种程序设计方法,还是一种建立客观事物模型、分析复杂事物的思想方法,它是以人们通常描述现实世界的方法来描述要解决的问题。面向对象是目前成熟并流行的软件工程方法之一,主要包括面向对象分析和面向对象程序设计。,结构化程序设计的特点:是一种自上而下、逐步求精的模块化程序设计方法。是一种面向过程程序设计方法,即一个程序是由多个过程(在C+中为函数)模块组成,过程之间通过函数参数和全局变量进行相互联系(算法+数据结构=程序)。与非结构化程序相比,结构化程序在调试、可读性和可维护性等方面都有很大的改进。代码重用性不高:以过程为中心设计新系统,除了一些标准函数,大部分代码都必须重新编写由于软、硬件技术的不断发展和用户需求的变化,按照功能划分设计的系统模块容易发生变化,使得开发出来的模块的可维护性欠佳。面向过程模式将数据与过程分离,若对某一数据结构做了修改,所有处理数据的过程都必须重新修订,增加了很多的编程工作量。,3.1.1结构化程序设计,3.1.2面向对象程序设计,面向对象程序设计:面向对象程序设计就是运用以对象作为基本元素的方法,用计算机语言描述并处理一个问题。面向对象程序设计符合客观世界本身的特点和人们分析问题的思维方式。什么是对象:客观世界是由各种各样的事物组成,包括真实的事物和抽象的事物。例如,人、动物、汽车(真实的事物)和程序、直线(抽象的事物)等。每一类事物都有自己特定的属性(如大小、形状、重量等)和行为(如生长、行走、转弯、运算等),人们通过研究事物的属性和行为而认识事物。,在计算机科学中,对象是系统中用来描述客观事物的一个实体,它是用于构成系统的一个基本单位,而系统可以看作是由一系列相互作用的对象组成。在程序设计领域,可以用如下公式表示:对象=数据+作用于这些数据上的操作,为了描述属性(又称状态)和行为(又称操作、方法)相同的一类对象,引入了类(class)的概念。类定义了同类对象的公共属性和行为。类=数据结构+对数据进行操作的函数对象是类的一个实例,例如,汽车是一个类,而行驶在公路上的一辆汽车则是一个对象。对象和类的关系相当程序设计语言中变量和变量类型的关系。,什么是类:,OOP围绕现实世界的概念来组织模块,采用对象描述问题空间的实体,用程序代码模拟现实世界中的对象,使程序设计过程更自然、更直观。SP是以功能为中心来描述系统,而OOP是以数据为中心来描述系统。相对于功能而言,数据具有更强的稳定性。OOP模拟了对象之间的通信。就象人们之间互通信息一样,对象之间也可以通过消息进行通信。这样,我们不必知道一个对象是怎样实现其行为的,只需通过对象提供的接口进行通信并使用对象所具有的行为功能。OOP把一个复杂的问题分解成多个能够完成独立功能的对象(类),然后把这些对象组合起来去完成这个复杂的问题。一个对象可由多个更小的对象组成,如汽车由发动机、传送系统和排气系统等组成。这些对象(类)可由不同的程序员来设计,可在不同程序中使用,就象一个汽车制造商使用许多零部件去组装一辆汽车,而这些零部件可能不是自己生产的。采用面向对象模式就象在流水线上工作,我们最终只需将多个零部件(已设计好的对象)按照一定关系组合成一个完整的系统。,面向对象程序设计的特点:,面向对象程序设计方法的基本特征,面向对象程序设计方法具有四个基本特征:抽象性、封装性、继承性、多态性,1.抽象抽象是人类认识问题的最基本手段之一。抽象是指对具体问题(对象)进行概括,抽出一类对象的公共属性和行为并加以描述的过程。抽象包括数据抽象和行为抽象。,2.封装封装是把每个对象的数据(属性)和操作(行为)包装在一个类中。一旦定义了对象的属性和行为,则必须决定哪些属性和行为只用于表示内部状态,哪些属性和行为在外部是可见的。一般限制直接访问对象的属性,而应通过操作接口访问,这样使程序中模块之间关系更简单、数据更安全。对程序的修改也仅限于类的内部,使得由于修改程序所带来的影响局部化。,3.继承继承是指一个新类可以从现有的类派生而来。新类继承了现有类的特性,包括一些属性和行为,并且可以修改或增加新的属性和行为,使之适合具体的需要。例如,所有的Windows应用程序都有一个窗口,它们可以看作都是从一个窗口类派生出来的,但有的应用程序用于文字处理,有的应用程序用于绘图,这是由于派生出了不同的类,它们增加了不同的属性和行为。继承很好地解决了软件的可重用性问题。,4.多态性多态性是指类中具有相似功能的不同函数使用同一个名称来实现,并允许不同类的对象对同一消息作出的响应不相同。例如,同样的“编辑|粘贴”操作,在字处理程序和绘图程序中有不同的结果;同样的加法,把两个时间值相加和把两个整数相加的要求肯定不同。多态性使程序设计灵活、抽象,具有行为共享和代码共享的优点,很好地解决了程序的函数同名问题。,学生类private:学号;姓名;public:信息设置;听讲;好学生类public:听讲认真;完成作业;,主函数学生类s1;s1.信息设置;s1.听讲;好学生类s2;s2.信息设置;s2.听讲;s2.完成作业;学生类*ps;ps=,为了支持面向对象程序设计,C+在C语言结构(struct)数据类型的基础上引入了类这种抽象数据类型。C+面向对象编程实质上就是面向类编程,只有定义和实现了类,才能声明属于这个类的对象,才能通过对象使用定义的成员。传统C程序员把编程重点放在函数的编写上,而C+程序员把重点放在类的定义和实现上。,3.2类与对象,structPointintx;inty;voidprint()coutx”访问对象的公有成员,但不能访问对象的私有成员。例如,公有成员函数调用:t1.setTime();start.showTime();pt1-setTime();而任何形如t1.hour、t1.minute、start.second等私有成员变量的直接访问都是非法的。,成员的访问,voidmain()TimeEndTime;/声明对象EndTimeEndTime.setTime(12,23,36);/设置对象EndTime的时间cout=0,Thetimeis:12:23:36,练习:P1223-38,在定义类时不能对成员变量进行初始化,因为无法确定成员变量属于哪一个对象。成员变量一般都定义为私有属性,也不能在声明对象后利用赋值运算对成员变量进行初始化。成员变量的初始化一般是利用一个名为构造函数的成员函数来完成。,3.2.2构造函数和析构函数,如何进行成员变量的初始化?,?,构造函数是一种特殊的成员函数,它是在创建对象时(声明或new动态创建)系统自动调用的成员函数。构造函数的作用就是在对象被创建时利用初始值去构造对象,使得在声明对象时就能自动地完成对象的初始化。,什么是构造函数:,不能指定任何返回值类型,包括void返回类型,没有返回值构造函数的名称与类名相同构造函数可以有任意类型的参数,可重载成员函数,可以在类体内实现,也可以在类体外实现在创建对象时由系统自动调用,程序中不能直接调用,析构函数也是一种特殊的成员函数,它是在对象生存期结束时系统自动调用的成员函数。析构函数的作用是在对象被删除前做一些清理工作和数据保存工作。例如:利用delete运算释放用new运算分配的内存单元,在关闭一个Windows窗口时可以通过调用析构函数保存窗口中的内容。,什么是析构函数:,不能指定任何返回值类型,包括void返回类型,没有返回值析构函数的名称在类名前加上“”符号析构函数无参,不能重载,一个类只能有一个析构函数成员函数,可以在类体内实现,也可以在类体外实现在删除对象时由系统自动调用,程序中不能直接调用,#includeclassTimeprivate:inthour;intminute;intsecond;public:Time(int,int,int);/构造函数Time();/析构函数.;,例3_4:为类Time添加构造函数和析构函数。,Time:Time(inth,intm,ints)hour=h;minute=m;second=s;/对私有成员变量初始化coutTheconstructorbecalled:hour:minute:secondendl;,Time:Time()coutThedestructorbecalled:hour:minute:secondendl;,功能与成员函数Time:setTime()类似,voidmain(void)Timet1(10,35,55);/自动调用构造函数Timet2(16,53,9);/自动调用构造函数/退出main()主函数时自动调用析构函数,构造函数和析构函数的自动调用:,Theconstructorbecalled:10:35:55Theconstructorbecalled:16:53:9Thedestructorbecalled:16:53:9Thedestructorbecalled:10:35:55,为什么是这个结果?,?,结果分析:,当创建一个对象时,系统先根据类定义的成员变量为对象分配内存空间,然后自动调用对象的构造函数对这段内存空间进行初始化处理,从而完成对象的初始化。,当撤消一个对象时,系统先自动调用对象的析构函数,然后释放对象所占内存空间。从程序的运行结果可以看出,析构函数的调用顺序一般与构造函数的调用顺序相反。栈:后进先出,与一般数据类型的变量相比,对象在它的生存期会有大量的操作,有时这些操作的结果必须在对象的生存期结束时加以清理。因此可以在析构函数中进行动态分配的内存清理工作。如果定义类时没有提供构造函数和析构函数,编译系统将会自动为类分别添加一个缺省的构造函数和析构函数。如果用户加上自定义的构造函数和析构函数,编译系统将不会再添加缺省的构造函数和析构函数。,补充说明:,类名();/缺省的构造函数类名(参数名);/参数形式的构造函数类名(const类名public:A()a=b=0;coutA1n;A(intm)a=b=m;coutA2n;A(intm,intn)a=m;b=n;coutA3n;voidprint()coutabendl;A()coutabendl;voidmain()Aa1,a2(10),a3(10,20);a1.print();a2.print();a3.print();,3.2.3静态成员,静态成员的概念:一般情况下,同一个类不同对象的数据成员所占用的内存空间是不同的(体现了不同对象具有不同的属性值)。在有些情况下,类的数据成员的值对每个对象都是相同的(类属性),如当前已创建对象的数量,这时可以将该成员声明为静态成员。静态成员分为静态数据成员和静态成员函数。,静态数据成员:静态数据成员类似于一般的static静态变量,它具有全局性。静态数据成员属于整个类,为类的所有对象共享。无论类的对象有多少,类的静态数据成员只有一份,存储在同一个内存空间。即使没有创建类的一个对象,类的静态数据成员也是存在的。使用静态数据成员保证了该数据成员值的唯一性。,静态数据成员的访问:公有静态成员:三种方式(1)通过对象访问,如:person1.m_nCount=100;(2)利用类名和作用域限定符(:)访问,如:intPerson:m_nCount=100;/初始化(3)在成员函数中访问,如:m_nCount+;私有和保护静态成员:只能在成员函数中访问,静态数据成员的声明:在声明成员时以关键字static开头,例如:staticintm_nCount;,静态成员的初始化:放在类定义的外部,格式为:数据类型类名:静态数据成员=值例如:intPerson:m_nCount=0;,静态成员函数:,有时需要在声明对象之前访问私有静态数据成员,这时必须定义一个访问静态数据成员的静态成员函数。声明方式与静态成员变量类似。public:staticintGetCount();静态成员函数也与一个类相关联,而不只与一个特定的对象相关联。区别非静态成员函数,静态成员函数没有this指针,因为类的静态成员函数只有一个运行实例。成员函数一般是公有属性,可以通过对象、类名和作用域限定符、在成员函数中三种方式调用静态成员函数。person1.GetCount();Person:GetCount();在Person成员函数中直接调用GetCount();,静态成员函数只能访问类的静态成员(成员变量和成员函数),而不能访问类的非静态成员。因为当通过类名和运算符“:”调用一个静态成员函数时,不能确定函数中所访问的非静态成员属于哪一个对象。解决方法:将对象作为静态成员函数的参数,然后在静态成员函数中通过对象访问它的非静态成员。,注意,例3_5:静态成员变量和静态成员函数的使用。,#include#includeclassPersonpublic:charm_strName20;longm_ID;,staticintm_nCount;/静态成员变量,表示已创建对象的数量public:Person(char*,long);/构造函数staticintGetCount();/静态成员函数staticlongGetID(Person);/对象作为静态成员函数的参数;Person:Person(char*strName,longID)strcpy(m_strName,strName);m_ID=ID;m_nCount+;/对象数目加1intPerson:GetCount()returnm_nCount;/访问静态成员变量longPerson:GetID(Personx)returnx.m_ID;/不能直接访问非静态成员m_ID,intPerson:m_nCount=0;/初始化静态成员变量voidmain()Persone1(LiuJun,1101051);coutPerson:m_nCount,e1.m_nCountn;/通过类或对象访问静态成员变量coutPerson:GetCount(),Person:GetID(e1)n;/通过类调用静态成员函数coute1.GetCount(),e1.GetID(e1)n;/通过对象调用静态成员函数Persone2(WangXiaogang,1101058);coutPerson:GetCount(),Person:GetID(e2)n;coute2.GetCount(),e2.GetID(e2)n;coute1.GetCount(),e1.GetID(e1)n;/e1和e2共享静态成员变量m_nCount,程序运行结果为:1,11,11010511,11010512,11010582,11010582,1101051,练习:P1233-39(1),3.2.4this指针,每个成员函数都隐藏有一个名为this指针的参数,它是一个特殊的指针,指向调用成员函数的对象。当通过一个对象调用成员函数(非静态成员函数)时,编译器要把对象的地址传递给this指针。在成员函数中访问数据成员或调用其他成员函数不需要指定对象,因为它们都是通过一个隐藏的this指针确定当前的对象。,下面定义的成员函数并没有声明this参数:,voidTime:showTime()couthour:minute:secondendl;,编译器会把this指针作为成员函数的参数:,voidTime:showTime(Time*this);couthourminutesecondendl;,调用时:,当程序中调用某个成员函数时,编译器会把该对象的地址赋值给this指针,并将该地址值加入到参数表中,如下所示:EndTime.showTime(,作用:,在一个成员函数中经常需要调用其它函数(非本类的成员函数),而有时需要把对象本身(即对象的地址)作为参数传递给被调用函数,这时必须使用this指针。,例3_6:this指针的使用。,#include#includeclassPersonpublic:/可在外部直接访问public属性的数据成员charm_strName20;,charm_ID18;public:Person(char*strName,char*ID)/内联构造函数strcpy(m_strName,strName);strcpy(m_ID,ID);voidShow();voidDisplay(Person*pObj)/非成员函数coutm_strNamem_IDShow();/通过调用Show调用Display,this指针在windows编程中相当有用。当调用其他类的成员函数时,如果想得到主调函数的对象的句柄(如windows中的窗口、控件、设备或文件等),可以使用this指针。voidCMyView:OnLButtonDown(UINTnFlags,CPointpoint)CClientDCdc(this);dc.LineTo(point);,3.2.5友元,类具有封装性,类的私有成员一般只能通过该类的成员函数访问,这种封装性隐藏了对象的数据成员,保证了对象的安全,但有时带来了编程的不方便。,友元函数:C+提供了一种函数,它虽然不是一个类的成员函数,但可以像成员函数一样访问该类的所有成员,包括私有成员和保护成员。这种函数称为友元(friend)函数。,友元函数的声明:,一个函数要成为一个类的友员函数,需要在类的定义中声明该函数,并在函数声明的前面加上关键字friend。,友元函数本身的定义没有什么特殊要求,可以是一般函数,也可以是另一个类的成员函数。为了能够在友元函数中访问或修改类的私有数据成员,一个类的友元函数一般将该类的对象或引用作为函数参数。,classAfriendvoiddisplay(A);/友元函数是一个一般函数friendvoidB:BMemberFun(A/友元函数是另一个类B的成员函数public:.,友元类:友元的另一种类型是友元类,一个类可以声明另一个类为其友元类,这个友元类的所有成员函数都可以访问声明其为友元的类的所有成员。classAfriendC;由于访问权限控制符不影响友元声明,友元声明可放在类体中任何地方,建议把友元声明放在类体的开始位置。,说明:友元关系是单方向的,不具有交换性和传递性。使用友元虽然简化了编程,并可避免调用成员函数的开销,但破坏了类的封装性,建议谨慎使用。,#includeclassA;classBpublic:voidBMemberFun(A,public:A(intx=0,inty=0)a=x;b=y;voiddisplay(Ae)couta=e.a,;coutb=e.bendl;voidB:BMemberFun(A,例3_7:友元的声明和使用,3.2.6常对象和常对象成员,类的封装保证了数据的安全性,但各种形式的数据共享(如静态成员和友元)却有不同程度的破坏了数据的安全性。对于既需要共享又需要安全的数据,可以利用const来进行保护。const还可用来修饰对象(常对象)、数据成员(常数据成员)和成员函数(常成员函数)。,1、const常对象,常对象的数据成员的值在对象的整个生存期内不能被改变,必须利用构造函数进行初始化,且以后不能再被更新。,const(初始值列表)const(初始值列表),constTimemeeting(8,30,00);,#includeclassTimeprivate:inthour,minute,second;public:Time(inth,intm,ints)hour=h;minute=m;second=s;voidchangeTime(inth,intm,ints)hour=h;minute=m;second=s;voidmain(void)Timeconstmeeting(8,30,00);meeting.changeTime(9,00,00);,例3_8:分析以下程序有什么错误。,2、常成员函数,可以使用const关键字限制成员函数对数据成员进行修改工作,这种使用const关键字进行声明的成员函数称为常成员函数。,()const;,举例:voidMemberFun()const;,由于常成员函数只能访问数据成员,不能设置或修改数据成员,因此,在常成员函数中只能调用常成员函数,而调用其他普通函数可能会造成间接修改数据成员。同理,通过常对象只能调用常成员函数,不能调用普通成员函数(构造函数除外),但通过普通对象可以调用常成员函数。,不修改对象中数据成员的函数可声明为常成员函数,常成员函数的声明格式如下:,#includeclassTimeprivate:inthour,minute,second;public:Time(inth,intm,ints)hour=h;minute=m;second=s;voidShowTime()const;/使用const声明常成员函数;voidTime:ShowTime()constcouthour:minute:secondendl;voidmain(void)Timeconstmeeting(8,30,00);meeting.ShowTime();/常对象只能调用常成员函数TimeclassTime(8,10,00);classTime.ShowTime();/普通对象可以调用常成员函数,例3_9:常对象和常成员函数的使用。,#includeclassTimeprivate:inthour,minute,second;public:Time()Time(inth,intm,ints)hour=h;minute=m;second=s;voidsettime(inth,intm,ints)hour=h;minute=m;second=s;intgethour()constreturnhour;intgetminute()constreturnminute;intgetsecond()constreturnsecond;voidprint()constcouthour:minute:secondendl;voidmain(void)Timet1;constTimet2(10,23,34);t1.settime(11,15,20);t2.settime(4,12,15);t1.print();t2.print();,3、常数据成员,如果某个数据成员的值是不应该被修改的,那么可以将其声明为const,使其受到强制保护,以防被意外改动。const数据类型变量名;对于常数据成员,任何函数都不能对它进行赋值。常数据成员的初始值只能通过构造函数获取,并且只能在构造函数的初始化列表中设置其初始值。Sample(intx,inty):b(y)说明:对非常数据成员也可以用初始化列表进行初始化。Sample(intx,inty):b(y),a(x)静态常数据成员的初始化只能在类体外进行。constintSample:c=100;,#includeclassSampleprivate:inta;constintb;staticconstintc;public:Sample(intx,inty):b(y)a=x;voidDisplay()cout“a=”a“,b=”b“,c=”cendl;constintSample:c=100;voidmain()SampleMyObject1(5,10);MyObject1.Display();SampleMyObject2(8,12);MyObject2.Display();,例3_10:常数据成员的声明和使用。,练习:P1233-39(3),P124:3-39(3)以下程序有什么错误?如有请予以修改。#includeclassSampleprivate:intn;public:Sample(intx)n=x;voidSetValue(intx)n=x;voidDisplay()coutn=nendl;voidmain()constSamplea(100);a.SetValue(0);a.Display();,习题50、52:建立一个名为Student的类,该类有以下几个私有成员变量:学生姓名、学号、性别和年龄。还有以下两个成员函数:一个用于初始化学生姓名、学号、性别和年龄的构造函数,一个用于输出学生信息的函数。编写一个主函数,声明一个学生对象,然后调用成员函数在屏幕输出学生信息。添加一个静态成员变量用于表示已创建对象的数量;添加两个静态成员函数,一个用于输出已创建对象的数量,一个用于输出一个学生的姓名和学号。,#include#includeclassStudentcharm_strName20;charm_ID20;charm_sex;intm_age;public:Student(char*,char*,char,int);voidPrintMsg();,Student:Student(char*strName,char*ID,charsex,intage)strcpy(m_strName,strName);strcpy(m_ID,ID);m_sex=sex;m_age=age;voidStudent:PrintMsg()cout学生姓名:m_strName学号:m_ID性别:;if(m_sex=M)cout男;elsecout女;cout年龄:m_ageendl;voidmain()Students(LiMing,110121112119,M,20);s.PrintMsg();,#include#includeclassStudentcharm_strName20;charm_ID20;charm_sex;intm_age;public:staticintmCount;Student(char*,char*,char,int);voidPrintMsg();staticvoidPrintCount();staticvoidPrintNameID(Student);intStudent:mCount=0;Student:Student(char*strName,char*ID,charsex,intage)strcpy(m_strName,strName);strcpy(m_ID,ID);m_sex=sex;m_age=age;mCount+;,voidStudent:PrintMsg()cout学生姓名:m_strName学号:m_ID性别:;if(m_sex=M)cout男;elsecout女;cout年龄:m_ageendl;voidStudent:PrintCount()cout当前学生人数为:mCountendl;voidStudent:PrintNameID(Studentx)cout学生姓名:x.m_strName学号:x.m_IDendl;voidmain()Students1(LiMing,110121112119,M,20);s1.PrintMsg();Students2(LiuJun,110121112120,N,19);s2.PrintMsg();Student:PrintCount();s1.PrintCount();Student:PrintNameID(s2);,3.3继承与派生,继承是面向对象程序设计方法的四个基本特征之一,是程序代码可重用性的具体体现。在C+面向对象程序设计中,所谓类的继承就是利用现有的类创建一个新的类。新类继承了现有类的属性和行为。为了使新类具有自己所需的功能,它可以扩充和完善现有类的属性和行为,使之更具体。,继承,发扬,微软基础类MFC就是通过类的继承来体现类的可重用性和可扩充性。,3.3.1基类和派生类,在现实世界中,一类事物的对象常常也属于另一类事物。在面向对象程序设计方法中,一个类的对象也常常是另一个类的对象,即一个类具有了另一个类的属性和方法。在定义一个类时,根据类的继承性,我们能够且应尽可能地利用现有的类来定制新的类,而不必重新设计新的类。,1.问题的提出,2.基类和派生类的概念,在继承关系中,新定义的类称为原有类的派生类或子类,而原有的类称为新定义类的基类或父类。派生类继承了基类的所有属性和行为,并且派生类可以对基类的行为进行修改和完善,还可以增加新的属性和行为。一个派生类也可以作为另一个派生类的基类。,class:./派生类新增加的成员声明列表;,3.派生类的定义,派生方式决定了基类的成员在派生类中的访问权限。派生方式共有三种:public、private和protected(缺省值为private)。虽然派生类继承了基类的所有成员,但为了不破坏基类的封装性,无论采用哪种派生方式,基类的私有成员在派生类中都是不可见的,即不允许在派生类的成员函数中访问基类的私有成员。,说明:,派生类成员的类型由继承方式和基类类型共同确定,采用public派生,基类成员的访问权限在派生类中保持不变,即基类所有的公有或保护成员在派生类中仍为公有或保护成员。public派生最常用。(1)可以在派生类的成员函数中访问基类的非私有成员;(2)可通过派生类的对象直接访问基类的公有成员。采用private私有派生,基类所有的公有和保护成员在派生类中都成为私有成员,只允许在派生类的成员函数中访问基类的非私有成员。private派生很少使用。采用protected保护派生,基类所有的公有和保护成员在派生类中都成为保护成员,只允许在派生类的成员函数和该派生类的派生类的成员函数中访问基类的非私有成员。,三种派生方式的区别:,例3-11:定义类Point(表示点),然后定义类Point的派生类Circle(表示圆)。,#includeclassPoint/定义基类,表示点private:intx;inty;public:voidsetPoint(inta,intb)x=a;y=b;/设置坐标intgetX()returnx;/取得X坐标intgetY()returny;/取得Y坐标;,classCircle:publicPoint/定义派生类,表示圆private:intradius;public:voidsetRadius(intr)radius=r;/设置半径intgetRadius()returnradius;/取得半径intgetUpperLeftX()returngetX()-radius;/取得外接正方形左上角的X坐标intgetUpperLeftY()returngetY()+radius;/取得外接正方形左上角的Y坐标;,voidmain()Circlec;c.setPoint(200,250);c.setRadius(100);coutX=c.getX(),Y=c.getY(),Radius=c.getRadius()endl;coutUpperLeftX=c.getUpperLeftX(),UpperLeftY=c.getUpperLeftY()endl;,公有派生类的对象可以直接访问基类Point的公有成员,程序运行结果:X=200,Y=250,Radius=100UpperLeftX=100,UpperLeftY=350,派生类Circle通过public派生方式继承了基类Point的所有成员(除私有成员外所有成员的访问权限不变),同时还定义了自己的成员变量和成员函数。若将类Circle的派生方式改为private或protected,则下述语句是非法的:c.setPoint(200,250);,说明:,!,容易混淆,无论哪种派生方式,派生类都继承了基类的所有成员,包括私有成员。我们虽然不能在派生类Circle中直接访问私有数据成员x和y,但可以通过继承的公有成员函数getX()、getY()和setPoint()访问或设置它们。,利用类继承定义类可能带来一个问题:派生类会继承它不需要的基类中的数据成员和成员函数,这时,基类中不适合于派生类的成员可以在派生类中重新加以定义。,问题:,?,例3-12:派生类成员函数对基类成员函数的覆盖。,#includeclassApublic:voidShow()coutA:Shown;,classB:publicApublic:voidShow()coutB:Shown;/在派生类中重新定义成员函数voidDisplay()Show();/调用派生类B的成员函数Show();voidmain()Aa;Bb;a.Show();/调用基类A的成员函数Show()b.Show();/调用派生类B的成员函数Show()b.Display();,如果想调用基类A的成员函数Show(),可以使用作用域限定符“:”:A:Show();,程序运行结果:A:ShowB:ShowB:Show,从本例可以看出,虽然派生类继承了基类的所有成员函数,但如果派生类某个成员函数的名称和参数与基类成员函数一致(即在派生类中对该成员函数重新进行了定义),则在派生类中调用的成员函数是派生类的成员函数。请问:如果在派生类B中没有对成员函数Show()重新进行定义,程序运行结果如何?,?,为什么我们经常在现有类的基础上采用继承的方法来定制新类,而不通过直接修改现有类来设计自己的类?除了代码重用的优越性,其主要原因是可能得不到基类的实现源码。,继承的重要性!,在利用微软基础类MFC派生自己的类时,我们只需要MFC类声明的头文件(利用#include指令将头文件包含)和含有成员函数目标代码的OBJ文件,并不需要整个MFC类库的实现源码。,3.3.2派生类的构造函数和析构函数,一个派生类对象也属于其基类,因此当程序创建一个派生类对象时,系统首先自动创建一个基类对象。基类的构造函数不能被继承,派生类的构造函数必须调用基类的构造函数来初始化基类的子对象。基类的析构函数也不能被继承,执行派生类的析构函数时,先执行派生类的析构函数,在执行基类的析构函数。在调用派生类的构造函数构建派生类对象时,系统首先调用基类的构造函数构建基类对象。当派生类对象的生存期结束时,首先调用派生类的析构函数,然后调用基类的析构函数。,1.问题的提出,编译器在对程序编译时,首先生成基类构造函数的调用代码,然后生成派生类构造函数的调用代码。,2.基类构造函数的调用方式,隐式调用和显式调用两种方式:,(1)隐式方式是指在派生类的构造函数中不指定对应的基类的构造函数,调用的是基类的默认构造函数。,#includeclassApublic:A()coutAn;classB:publicApublic:B()coutBn;,voidmain()Bb;,注意:除非基类有默认的构造函数,否则必须采用显式调用方式。,(2)显式方式是指在派生类的构造函数中指定要调用的基类构造函数,并将派生类构造函数的部分参数值传递给基类构造函数。,!,3.显式方式构造函数的定义,设类B是类A的派生类,则派生类B显式方式构造函数的定义形式如下:,B:B():A()./类B构造函数的实现代码,派生类构造函数既初始化派生类的数据成员,又通过基类构造函数初始化其基类的数据成员。参数表中参数的个数和类型要与基类某个构造函数的形参声明一致。,形参声明中的部分参数,传递给基类构造函数,派生类构造函数形参的名称和类型,举例:Point(inta,intb)x=a;y=b;Circle(intm,intn,intr):Point(m,n)radius=r;Circlec(100,200,300);,注意:当基类有多个构造函数时,编译器根据派生类构造函数为基类构造函数提供的参数表来确定调用基类的哪一个构造函数。,PointCircleCylinder,例3-13:首先定义类Point,然后定义类Point的派生类Circle,再定义类Circle的派生类Cylinder。,#includeclassPoint/定义基类Pointprotected:intx,y;public:Point(inta=0,intb=0)/含有缺省参数值的构造函数也是默认的构造函数x=a;y=b;coutPointconstructor:x,yendl;Point()coutPointdestructor:x,yendl;classCircle:publicPoint/定义类Point的派生类protected:intradius;,public:/显式调用基类的构造函数Circle(inta=0,intb=0,intr=0):Point(a,b)radius=r;coutCircleconstructor:x,y,radiusendl;Circle()coutCircledestructor:x,y,radiusendl;classCylinder:publicCircle/定义类Circle的派生类protected:intheight;public:/显式调用基类的构造函数Cylinder(inta=0,intb=0,intr=0,inth=0):Circle(a,b,r)height=h;coutCylinderconstructor:x,y,radius,heightendl;,Cylinder()coutCylinderdestructor:x,y,radius,heightendl;voidmain()Cylindercylinder(400,300,200,100);/调用了类Point、Circle和Cylinder的构造函数,程序运行结果:,构造函数的执行顺序:,析构函数的执行顺序:,Point(),Circle(),Cylinder(),当声明Cylinder对象时,Cylinder(),Circle(),Point(),当程序结束时,举例:#includeclassApublic:A()coutA;A()coutA;classB:publicApublic:B()coutB;,B()coutB;voidmain()Bobj;,练习:P1233-39(2),3.3.3多重继承,1.单继承和多重继承的概念,classA,classB,classC,classA,classB,classC,一个派生类只有一个直接基类单继承,一个派生类同时从多个基类派生而来,即有多个直接基类多重继承,2.多重继承派生类的定义,设类B是类A1、A2、An的派生类,多重继承的派生类的定义形式为:,class:,./派生类新增加的成员声明列表;,多重继承的派生方式也有private、public和protected三种,各基类的派生方式可以不同,例3-14:定义一个派生类MultiDerived,它是类BaseA和BaseB的派生类。,classBaseA/定义基类protected:inta;public:voidsetA(int);,classBaseB/定义基类protected:intb;public:voidsetB(int);,定义两个基类,classMultiDerived:publicBaseA,publicBaseB/定义多重继承的派生类public:intgetAB();/添加成员函数;,voidBaseA:setA(intx)a=x;voidBaseB:setB(intx)b=x;intMultiDerived:getAB()returna+b;,可以直接访问基类中protected属性成员,成员函数的实现,voidmain()MultiDerivedmd;/声明派生类的对象md.setA(30);/调用基类BaseA的成员函数md.setB(70);/调用基类BaseB的成员函数couta+b=md.getAB()endl;/调用派生类MultiDerived自定义的成员函数,程序运行结果:a+b=100,3.派生类构造函数的执行,多继承下调用派生类的构造函数时,首先要调用该派生类所有基类的构造函数。而处于同一层的各基类构造函数的执行顺序取决于定义派生类时各基类的排列顺序,与定义派生类的构造函数时基类构造函数的排列顺序无关。,例3-15:从下面的程序中观察多继承方式下构造函数的执行顺序。,#includeclassBase1private:inta;public:Base1(intx)a=x;coutBase1Constructor!endl;intgeta()returna;,classBase2private:intb;public:Base2(intx)b=x;coutBase2Constructor!endl;intgetb()returnb;,classDerived:publicBase1,publicBase2private:intc;public:Derived(intx,inty,intz):Base2(z),Base1(y)c=x;coutDerivedConstructor!endl;voidshow()coutgeta()getb()cendl;,voidmain()Derivedobj(1,2,3);obj.show();,#includeiostream.hconstdoublePI=3.14;classCircle/定义基类doubleradius;public:Circle(doubler)radius=r;coutCirclet;doubleCircleArea()returnPI*radius*radius;/求圆面积Circle()coutCirclet;classRectangle/定义基类doublelength,width;public:Rectangle(doublex,doubley)length=x;width=y;cout“Rectanglet”;doubleRecArea(void)returnlength*width;/求矩形面积Rectangle()coutx;couty;Graphg(r,x,y);g.ShowArea();,3.3.4虚基类,1.多重继承中的二义性问题,classBase1/定义基类protected:inta;/与基类2的成员同名public:voidset(intx)a=x;/与基类2的成员同名;classBase2/定义基类protected:inta;/与基类1的成员同名public:voidset(intx)a=x;/与基类1的成员同名;classMultiDerived:publicBase1,publicBase2/定义多重继承的派生类public:intget()returna;/二义性错误:Base1的a还是Base2的a;voidmain()MultiDerivedmd;md.set(30);/二义性错误:Base1的set()还是Base2的set(),例3-16:多重继承中的二义性问题之一,ReturnBase1:a;,md.Base1:set(30);,classC:publicApublic:intc;classD:publicB,publicC/类D派生于类B和类Cpublic:intd;voidmain()Dd1;d1.a=100;,classApublic:inta;classB:publicApublic:intb;,二义性错误:编译器无法确定数据成员a是哪一个副本,?,例3-17:多重继承中的二义性问题,classB,classC,classD,classA,派生类D的对象中存在间接基类A的两份副本,2.解决方法,利用作用域限定符(:)把基类的成员与下一层基类关联起来:d1.B:a=100;d1.C:a=100;,从路径DBA继承而来,从路径DCA继承而来,练习:P1233-40,缺点:浪费了存储空间,大部分情况下不需要保存基类多个相同的副本。在访问基类的成员时,要求指明访问路径。,3.使用虚基类,虚基类并不是一种新的类型的类,而是一种派生方式。采用虚基类方式定义派生类,在创建派生类的对象时,类层次结构中虚基类的成员只出现一次,即基类的一个副本被所有派生类对象所共享。,虚基类派生方式的定义:,例3-18:采用virtual虚基类方式定义派生类。,classB:virtualpublicApublic:intb;,classC:publicvirtualApublic:intc;,主函数中:d1.a=100;,采用虚基类方式定义派生类的方法是在基类的前面加上关键字virtual,而定义基类时与一般基类完全
展开阅读全文
相关资源
相关搜索

当前位置:首页 > 图纸专区 > 课件教案


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!