版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、第六讲第六讲 电子信息学院课程组武汉大学面向对象程序设计面向对象程序设计本讲主要内容本讲主要内容多态性多态性动态编联动态编联/静态编联静态编联虚函数虚函数/纯虚函数纯虚函数重载重载3继承与派生 回顾class Aclass Bclass C每个派生类只有一个直每个派生类只有一个直接基类接基类 单继承单继承class Aclass Bclass C一个派生类同时从多一个派生类同时从多个基类派生而来,即个基类派生而来,即有多个直接基类有多个直接基类 多重继承多重继承 C+概述继承与派生 回顾class Bclass Cclass Dclass ADBACA派生类派生类D的对象中存在间的对象中存在间
2、接基类接基类A的两份副本的两份副本 继承与派生 回顾 1.1.多重派生多重派生 虚基类虚基类 变量变量 2.2.派生派生 虚函数虚函数 操作操作 例:例:首先定义类Point,然后定义类Point的派生类Circle,再定义类Circle的派生类Cylinder。PointCircle Cylinder(x, y)(x, y)r(x, y)hr(x, y)whRectangle类描述8多态性l多态性是指类中同一函数名对应多个具有相似功能的不同函数,可以使用相同的调用方式来调用这些具有不同功能的同名函数的特性。l在C+程序中,多态性表现为同一种调用方式完成不同的处理。l从实现角度来划分,多态可分
3、为编译时多态和运行时多态。多态的表现 多态表现为函数名相同,可实现不同的功能。wash_car();wash (car);wash_clothes(); wash (clothes); wash_hands(); wash (hands);编译时多态编译时多态是指在编译阶段由编译系统根据操作数据确定调用哪个同名函数。运行时多态运行时多态是指在运行阶段才根据产生的信息确定需要调用哪个同名的函数。联 编C+通过采用联编技术来支持多态性。多态性实现过程中,确定调用哪个同名函数的过程就是联编,又称绑定。联编是指计算机程序自身彼此关联的过程,也就是将函数调用语句与函数代码相关联。两种联编方式:静态联编和
4、动态联编。 静态联编是指编译器在编译阶段就确定了要调用的函数,即早期绑定。重载采用静态联编方式。 动态联编是指在程序执行过程中根据具体情况再确定要调用的函数,即后期绑定。当通过基类指针调用虚函数时,C+采用动态联编方式。虚函数体现出一种动态多态性或运行时多态性。静态联编在编译阶段完成的联编称为静态联编。在编译过程中,编译系统可根据参数不同来确定哪一个同名函数。函数重载和运算符重载就是通过静态联编方式实现编译时多态性的体现。优点:函数调用速度快,效率高。缺点:编程不够灵活。静态联编举例#include class Studentpublic:void print() coutA Studente
5、ndl;class GStudent:public Studentpublic:void print() coutA graduate Studentprint( );ps-print( );ps=&s2;ps=&s2;ps-print( );ps-print( ); 希望调用对象希望调用对象s2s2的的输出函数输出函数 ,但是,但是实际调用的却是对实际调用的却是对象象s1s1的输出函数。的输出函数。运行结果:运行结果:A StudentA StudentA graduateA graduateA StudentA StudentA StudentA Student A Stu
6、dentA StudentA StudentA Student静态联编举例(续)说明说明: :基类指针基类指针psps指向派生类对象指向派生类对象s2s2时并没有调用时并没有调用派生类的派生类的print()print(),而仍然调用基类的,而仍然调用基类的print()print(),这是因为静态联编的结果。,这是因为静态联编的结果。在程序编译阶段,基类指针在程序编译阶段,基类指针psps对对print()print()的操的操作只能绑定到基类的作只能绑定到基类的print()print()上,导致程序上,导致程序输出了不期望的结果。而期望的是执行派生输出了不期望的结果。而期望的是执行派生类
7、的类的printprint函数。函数。用基类指针指向派生类对象l 声明一个派生类的对象的同时也自动声明了一个基类的对象。 l 派生类的对象可以认为是其基类的对象。C+允许一个基类对象的指针指向其派生类的对象 这是实现虚函数的关键l 不允许派生类对象的指针指向其基类的对象。l 即使将一个基类对象的指针指向其派生类的对象,通过该指针也只能访问派生类中从基类继承的公有成员,不能访问派生类自定义的成员,除非通过强制类型转换将基类指针转换为派生类指针。 17例 基类指针与派生类指针之间的相互转换。class Aprivate: int a;public: void setA(int i) a=i; vo
8、id showA() couta=an; ;class B : public Aprivate: int b;public: void setB(int i) b=i; void showB() coutb=bsetA(100); pa-showA(); pb=(B*)pa;/ 将基类指针强制转化为派生类指针 / 不能通过基类指针pa访问派生类自己定义的成员 pb-setB(200); pb-showB();程序运行结果为:程序运行结果为:a=100a=100b=200 b=200 pb=&a pb=&a 或者或者pa-setB()pa-setB()pa-showB() pa-
9、showB() 动态联编v有些联编工作无法在编译阶段准确完成 ,只有在运行程序时才能确定将要调用的函数。这种在运行阶段进行的联编称为动态联编。vC+规定,动态联编通过继承和虚函数来实现。v优点:提供了更好的编程灵活性、问题抽象性和程序易维护性。v缺点:与静态联编相比,函数调用速度慢。为什么要引入虚函数class Apublic: void Show( ) coutShow();class B : public Apublic: void Show( ) coutB:Shown; ; 调用哪一个Show()如果想通过基类指针调用如果想通过基类指针调用派生类中覆盖的成员函数派生类中覆盖的成员函数,
10、只有使用,只有使用虚函数虚函数。 虚函数 虚函数是动态联编的基础,是非静态的成员函数,经过派生之后,虚函数在类族中可以实现运行时多态。 虚函数是在某一个基类中声明为virtual,并在一个或多个派生类中被重新定义的成员函数。 声明虚函数的格式:virtual (); 一个函数一旦被声明为虚函数,则无论声明它的类被继承了多少层,在每一层派生类中该函数都保持虚函数特性。因此在派生类中重新定义该函数时,可省略关键字virtual。但是为了提高程序的可读性,往往不省略。class Apublic: virtual void Show() coutA:shown; ;class B : public A
11、public: void Show() coutShow(); / 调用函数A:Show() pa=&b; pa-Show(); / 调用函数B:Show() 程序运行结果程序运行结果: A:Show B:Show使用虚函数的注意事项 在派生类中重新定义虚函数时,必须保证函数的返回值类型和参数与基类中的声明完全一致。否则不具备多态性。 如果在派生类中没重新定义虚函数,则派生类的对象将使用基类的虚函数代码。 只有通过对象指针或对象引用来调用虚函数,才能实现动态联编。如果采用对象来调用虚函数,则采用的是静态联编方式。动态联编举例#include class Studentpublic:vi
12、rtual void print()coutA Student;coutendl;class GStudent:public Studentpublic:virtual void print()coutA graduate Student; coutprint(); 程序运行结果程序运行结果:A StudentA StudentA graduate StudentA graduate StudentA graduate StudentA graduate Student构造函数、析构函数与虚函数 基于构造函数的特点,不能将 构造函数定义为虚函数。 当撤消派生类的对象时,先调用派生类析构函数,然
13、后自动调用基类析构函数,如此看来析构函数没必要定义为虚函数。但是,假如使用基类指针指向其派生类的对象,而这个派生类对象是用new运算创建的。当程序使用delete运算撤消派生类对象时,这时只调用了基类的析构函数,而没有调用派生类的析构函数。 如果使用虚析构函数,无论指针所指的对象是基类对象还是派生类对象,程序执行时都会调用对应的析构函数。声明派生类对象时自动声明派生类对象时自动调用基类的构造函数调用基类的构造函数例 虚析构函数的使用class Apublic: / 构造函数不能是虚函数A() ; / 析构函数是虚函数 virtual A() coutA:destructorn;class B
14、: public Apublic:B() ; / 虚析构函数B() coutB:destructorn; ;void main()void main() A A * *pA=new B;pA=new B;/ . . . . . . / . . . . . . delete pAdelete pA; ;/ /* *先调用派生类先调用派生类B B的析构函数的析构函数,再调用基类,再调用基类A A的析构函数的析构函数* */ / 程序运行结果:程序运行结果: B:destructor A:destructor 如果析构函数不是虚函数,如果析构函数不是虚函数,则得不到下面的运行结果。则得不到下面的运行
15、结果。请思考会是什么结果请思考会是什么结果 总结总结:由于使用了虚析由于使用了虚析构函数,当撤消构函数,当撤消pApA所指派生所指派生类类B B的对象时,首先调用派的对象时,首先调用派生类生类B B的析构函数,然后再的析构函数,然后再调用基类调用基类A A的析构函数。的析构函数。进入下一节进入下一节虚函数的适用性 一般来说,可将类族中的具有共性的成员函数声明为虚函数,而具有个性的函数则没有必要声明为虚函数。但是以下情况例外:静态成员函数不能声明为虚函数。因为静态函数不属于某一个对象,没有多态性的特征。内联函数不能声明为虚函数。因为内联函数的执行代码明确,没有虚函数的特征。构造函数不能声明为虚函
16、数。构造函数不能声明为虚函数。析构函数可以是虚函数。#include class Pointpublic:Point(int x1,int y1) x=x1; y=y1; int area() return 0; private:int x,y; void fun(Point &p) coutp.area()endl;class Rect :public Pointpublic:Rect(int x1,int y1,int u1,int w1) :Point(x1,y1) u=u1; w=w1; int area() return u*w;private:int w,u;void ma
17、in()Rect rct(2,4,10,6);fun(rct);30虚函数实现同一个接口来处理不同的对象CShapeCShapeCEllipseCEllipseCCircleCCircleCTriangleCTriangleCRectangleCRectangleCSquareCSquarevoid main() CShapeaShape;CEllipseaEllipse;CCircleaCircle;CTriangleaTriangle;CRectangleaRect;CSquareaSquare;CShape *pShape6 = & aShape,& aEllipse,&
18、amp; aCircle,& aTriangle,& aRect,& aSquare,;for(int i=0; iDraw();虚函数使用实例:图形绘制RectTriShape 在图形类层次体系中有如下行为 Rotate Draw Arean希望通过指向基类Shape对象的指针统一完成绘制不同图形的工作Circle虚函数使用实例:图形绘制class Shapepublic:double t;int ntype;double s;Circle* pCir;Tri* pTri;CRect* pRect;public:void area();void area(Shape*
19、,int);class Circleprivate:double r ;double s;public:Circle(int a)r=a;static int ntype; void area()s = PI * r * r; coutthe area of cir is: sendl;int Circle:ntype=0;虚函数使用实例:图形绘制class CRectprivate:double wi,hi; double s;double xl, yl;public:static int ntype;CRect(double w,double h)wi=w;hi=h;void area()
20、 s = hi* wi; coutthe area of cir is: sendl;int CRect:ntype=1;虚函数使用实例:图形绘制class Triprivate:double a,b,c ;double s;public:static int ntype;Tri(double ai,double bi, double ci)a=ai;b=bi;c=ci;void area() double p=(a+b+c)/2;s = sqrt(p*(p-a)*(p-b)*(p-c); coutthe area of cir is: sarea();break;case 1:pRect =
21、 (CRect *)pShape;pRect-area();break;case 2:pTri = (Tri *)pShape;pTri-area();break;default:break;这种处理方式中,这种处理方式中,area函数函数必须理解现存的所有形状,必须理解现存的所有形状, 当有新的类加入系统,则处当有新的类加入系统,则处理形状的所有操作都必须修理形状的所有操作都必须修改。如果无法接触新类的代改。如果无法接触新类的代码,则无法实现新类的绘制码,则无法实现新类的绘制。虚函数使用实例:图形绘制int main()Tri triobj(3,4,5);CRect rectobj(4,5)
22、;Circle cirobj(5);Shape* pshobj;Shape shobj;pshobj = (Shape *)&rectobj;shobj.area(pshobj,rectobj.ntype); return 0; / 运行结果:运行结果:The area of circle is :20基于多态和虚函数,我们可以使得程序大为简化。虚函数使用实例:图形绘制class Shape point center;color col; virtual void draw(); virtual void rotate(); virtual void area();int main()
23、CRect rectobj(4,5);Circle cirobj(5); Shape* pshobj;pshobj = &rectobj;shobj-area();return 0; / 首先定义一个类首先定义一个类shape描述所有形状描述所有形状的普遍性质,在这的普遍性质,在这里只给出调用接口,里只给出调用接口,不给出具体实现。具不给出具体实现。具体实现在特定的具体体实现在特定的具体类中给出类中给出.虚函数使用实例:图形绘制void AreaAll(shape* v, int size) for(int I = 0; i Draw();有了这个定义有了这个定义,可以给出一,可以给出
24、一个对各种形状个对各种形状进行求面积的进行求面积的通用函数。通用函数。关于虚函数的说明 利用虚函数可以在基类和派生类中使用相同的函数名和参数类型,但定义不同的操作。这样,就为同一个类体系中所有派生类的同一类行为(其实现方法可以不同)提供了一个统一的接口。 例如,在一个图形类继承结构中,设类CShape是所有具体图形类(如矩形、三角形或圆等)的基类,则函数调用语句“pShape-Draw()”可能是绘制矩形,也可能是绘制三角形或圆。具体绘制什么图形,取决于pShape所指的对象。 有时在声明一个基类时无法为虚函数定义其具体实现,这时可以将其声明为纯虚函数。包含纯虚函数的类成为抽象类。抽象类 抽象
25、类是类的一些行为(成员函数)没有给出具体定义的类,即纯粹的一种抽象。 抽象类专门作为基类派生新类,只能用于类的继承,自身无法实例化,不能用来创建对象,主要是为一个类族提供统一的操作接口,抽象类又称为抽象基类。 抽象基类只提供了一个框架,仅仅起着一个统一接口的作用,而很多具体的功能由派生出来的类去实现。 通过抽象类为一个类族建立一个公共的接口,这个公共的接口就是纯虚函数。在一般的类库中都使用在一般的类库中都使用了抽象基类,如类了抽象基类,如类CObjectCObject就是微软基础类就是微软基础类库库MFCMFC的抽象基类。的抽象基类。抽象类的定义 一个类如果满足以下两个条件之一就是抽象类:至少
26、有一个成员函数不定义具体的实现;定义了一个protected属性的构造函数或析构函数。 抽象类不能用作参数类型、函数返回值类型或显式转换的类型。 可以说明指向抽象类的指针或引用,该指针或引用可以指向抽象类的派生类,进而实现多态性。纯虚函数纯虚函数纯虚函数 不定义具体实现的成员函数称为纯虚函数。纯虚函数不能被调用,仅起提供一个统一接口的作用。 纯虚函数的声明:virtual ()= 0 ; 当基类是抽象类时,只有在派生类中重新定义基类中的所有纯虚函数,该派生类才不会再成为抽象类。#include const double PI=3.14159;class CShapespublic: void
27、SetValue(int d,int w=0) x=d; y=w; virtual void Area()=0; void Disp() cout面积:sSetValue(10,5);ptr0-Area();/抽象类指针指向派生类成员函数ptr0-Disp();ptr1=&c1; /抽象类指针指向派生类对象ptr1-SetValue(10);ptr1-Area(); /抽象类指针指向派生类成员函数ptr1-Disp();#include const double PI=3.14159;class CShapespublic: void SetValue(int d,int w=0) x
28、=d; y=w; virtual void Area()=0; void Disp() cout面积:sendl; protected: int x,y; double s; class CLine: public CShapesprotected: int x1,y1; ;void main() CLine l; 有没有 错误?进入下一节进入下一节重 载vC+重载分为函数重载和运算符重载,这两种重载的实质是一样的。v通过使用重载机制,可以对一个函数名(或运算符)定义多个函数(或运算功能),只不过要求这些函数的参数(或参加运算的操作数)的类型有所不同。v重载使C+程序具有更好的可扩充性。49函
29、数重载v函数重载函数重载:指一组功能类似但函数参数类型(个数)不同的函数可以共用一个函数名。v当C+编译器遇到重载函数的调用语句时,它能够根据不同的参数类型或不同的参数个数选择一个合适的函数。v重载函数的种类: 构造函数 成员函数 一般函数重载的实现 函数的定义: () 构成重载的条件:函数名相同,函数的参数类型或个数不同。 不能用函数类型来重载函数。必须相同必须不同重载普通函数 float area(float a, float b, float c) ; /求三角形面积 float area (float w, float h); /求矩形面积重载成员函数class point priva
30、te:int x, y;public:point(int x, int y);void offset(int ox, int oy);void offset(int oxy);重载成员函数point:point(int x, int y) this-x = x;this-y = y;point:offset(int ox, int oy) x += ox;y += oy;point:offset(int oxy) x += oxy;y += oxy;重载构造函数 构造函数重载能给程序设计带来很大的灵活性。 可以自由设定声明对象时的初始条件 point pt1(10, 20), pt2; pt
31、2 = pt1.offset(5, 8); 程序设计的需要 当对象作为一个类的成员时,需要一个不带参数的构造函数重载构造函数class point private:int x, y;public:point() ;point(int x, int y) this-x = x;this-y=y;class line private:point pt1, pt2;public:line(point pt1, point pt2);line(int x1, int y1, int x2, int y2);main() int i=100; coutabs(i)endl; / int型 float f
32、=-125.78F; coutabs(f)endl; / float型 例 通过函数参数类型的不同实现函数重载。int abs(int val) r e t u r n v a l 0 ? val : val;float abs(float val) return (valx = x;this-y = y;point operator+(const point &pt) return point(x+pt.x, y+pt.y);int main(int argc, char* argv)point pt1(10, 20), pt2(1, 2);pt2 = pt1+pt2;return
33、0;重载-运算符(普通函数)class point friend point operator+(const point &pt1, const point &pt2);private:int x, y;public:point(int x, int y) this-x = x;this-y = y;point operator+(const point &pt1, const point &pt2) return point(pt1.x+pt2.x, pt1.y+pt2.y);int main(int argc, char* argv)point pt1(10
34、, 20), pt2(1, 2);pt2 = pt1+pt2;return 0;C+概述友元的种类F友元函数F友元成员F友元类C+概述65需要友元的目的l有时候普通函数需要直接访问一个类的保护或私有数据成员,但是又不希望任何函数都能无约束地访问。l普通函数需要直接访问类的保护或私有数据成员的原因主要是为了提高效率。C+概述66友元函数l C+提供了一种函数,它虽然不是一个类的成员函数,但可以象成员函数一样访问该类的所有成员,包括私有成员和保护成员。这种函数称为友元(friend)函数。l 友元函数的声明一个函数要成为一个类的友员函数,需要在类的定义中声明一个函数要成为一个类的友员函数,需要在类
35、的定义中声明该函数,并在函数声明的前面加上关键字该函数,并在函数声明的前面加上关键字friendfriend。友元函数本身的定义没有什么特殊要求,可以是一般函数,友元函数本身的定义没有什么特殊要求,可以是一般函数,也可以是另一个类的成员函数也可以是另一个类的成员函数( (这时也称为友元成员这时也称为友元成员) )。为了能够在友元函数中访问并设置类的私有数据成员,一个为了能够在友元函数中访问并设置类的私有数据成员,一个类的友元函数一般将该类的引用作为函数参数。类的友元函数一般将该类的引用作为函数参数。C+概述67举 例class A friend void display(A);/ 友元函数是一
36、个一般函数 friend void B:BMemberFun(A&); / 友元函数是另一个类B的成员函数(友元成员)public:. . . 关于友元的几点说明l 友元关系是单方向的,不具有交换性和传递性。l 友元本身不是类的成员。l 友元可以访问类的私有成员和保护成员。l 使用友元虽然简化了编程,并可避免调用成员函数的开销,但破坏了类的封装性,建议谨慎使用。重载-运算符(前置+,-)class point private:int x, y;public:point(int x, int y) this-x = x;this-y = y;point operator+() x += 1;y += 1;return *this;int main(int argc, char* argv)point pt1(10, 20), pt2(1, 2);pt2 = +pt1;return 0;重载-运算符(后置+,-)class point private:int x, y;public:point(int x, int y) this-x = x;this
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 离心泵课程设计感想
- 车辆课程设计重卡
- 阶梯圆筒拉深课程设计
- 软件工程课程设计课本
- 电气工程本科课程设计
- 2024-2030年中国豆浆机市场盈利竞争分析与未来决策建议研究报告
- 2024-2030年中国腈纶纤维市场发展现状及营运态势分析研究报告
- 2024-2030年中国照明电器市场营销策略及投资战略研究研究报告
- 2024-2030年中国智慧社区行业运营状况与投资可行性研究报告
- 2024-2030年中国二聚酸行业应用趋势与投资动态预测报告
- 2024年(学习强国)思想政治理论知识考试题库与答案
- 对比剂相关的急性肾损伤
- 消毒记录台账
- 伏安法和电位溶出法
- 随机过程教学大纲
- 费曼学习法PPT课件
- 植生孔技术专项施工方案.doc
- 工程结算书(完整版)
- 常用钢材磁特性曲线
- 浅谈地铁通信系统漏缆施工
- 机器人学_机器人雅可比矩阵
评论
0/150
提交评论