C++课件C++_第十章_第1页
C++课件C++_第十章_第2页
C++课件C++_第十章_第3页
C++课件C++_第十章_第4页
C++课件C++_第十章_第5页
已阅读5页,还剩37页未读 继续免费阅读

下载本文档

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

文档简介

1,第十章 多态性,10.1 多态性 10.2 虚函数 10.3 纯虚函数和抽象类 10.4 运算符重载,2,10.1 多态性,10.1.1多态的概念与分类 1. 多态的概念 在面向对象的概念中,多态性是指不同对象接收到相同消息时,根据对象类的不同产生不同的动作。多态性提供了同一个接口可以用多种方法进行调用的机制,从而可以通过相同的接口访问不同的函数。具体地说,就是同一个函数名称,作用在不同的对象上将产生不同的操作。 多态性提供了把接口与实现分开的另一种方法,提高了代码的组织性和可读性,更重要的是提高了软件的可扩充性。,3,2. 编译时的多态性和运行时的多态性 联编也称绑定,是指源程序在编译后生成的可执行代码经过连接装配在一起的过程。联编分为两种:静态联编和动态联编。 (1)静态联编:在运行前就完成的联编,又称前期联编。这种联编在编译时就决定如何实现某一动作,因此要求在程序编译时就知道调用函数的全部信息。这种联编类型的函数调用速度很快,效率也很高。 (2)动态联编:在运行时动态地决定实现某一动作,又成后期联编。这种联编要到程序运行时才能确定调用哪个函数,提供了更好的灵活性和程序的易维护性。,4,10.1.2 函数重载实现多态 函数的重载也称多态函数,是实现编译时的多态性的形式之一。它使程序能用同一个名字来访问一组相关的函数,提高了程序的灵活性。 函数重载时,函数名相同,但函数所带的参数个数或数据类型不同,编译系统会根据参数来决定调用哪个同名的函数。 面向对象程序设计中,函数的重载表现为两种情况: 第一种是参数个数或类型有所差别的重载 第二种是函数的参数完全相同但属于不同的类,5,当函数的参数完全相同但属于不同的类时,为了让编译能正确区分调用哪个类的同名函数,采用两种方法: 用对象名区别。 在函数名前加上对象名来限制。 用类名和作用域运算符加以区别 在函数名前加“类名”来限制。,6,class Student public: float calcTuition( ) ; class GraduateStudent:public Student public: float calcTuition( ) ; void main Student s; GraduateStudent gs; s.calcTuition(); gs.calcTuition(); ,7,作一些改动如下: void fn(Student ,8,class point int x,y; public: point(int xx,int yy) x=xx; y=yy; float area( ) return 0.0; ; class circle:public point int r;,9,public: circle(int xx,int yy,int rr):point(xx,yy)r=rr; float area( )return 3.1416*r*r; float area(float pi)return pi*r*r; ; void main( ) point pob(15,15); circle cob(20,20,10); coutpob.area( )endl; coutcob.area( )endl; coutcob.area(3.14)endl; coutcob.point:area( )endl; ,10,10.2 虚函数,class B public: void fun( ) cout“B:fun“endl; ; class D:public B public: void fun( ) cout“D:fun“endl; ;,void main( ) B b1,*pb= ,观察结果:重定向到派生类后的对象指针pb仍然指向基类, 这并不是指针重定向后希望的操作 解决方法:虚函数,11,派生类对象指针使用时应注意以下问题: 声明为指向基类对象的指针可以指向它的公有派生类的对象,但不允许指向它的私有派生类的对象。 允许声明为指向基类对象的指针指向它的公有派生类的对象,但不允许将一个声明为指向派生类对象的指针指向基类的对象。 声明为指向基类对象的指针,当其指向它的公有派生类的对象时,只能直接访问派生类中从基类继承下来的成员,不能直接访问公有派生类中定义的成员。要想访问其公有派生类中的成员,可将基类指针用显式类型转换方式转换为派生类指针。,12,10.2.2 虚函数的声明 1. 虚函数类内定义的一般形式为: virtual 函数类型 函数名(参数表) 函数体 2. 虚函数类内声明,类外定义的一般形式为: virtual 函数类型 函数名(参数表); /声明 函数类型 类名:函数名(参数表) /定义 函数体 ,虚函数的实质: 1.运行时根据对象调用相应的对象 所属的类的成员函数; 2.虚函数从父到子是一个 父类中定义有虚函数时,子类一般要重载,13,class B public: virtual void fun( ) cout“B:fun“endl; ; class D:public B public: void fun( ) cout“D:fun“endl; ;,void main( ) B b1,*pb= ,14,10.2.3 虚函数的限制 只有类的成员函数才能说明为虚函数。这是因为虚函数仅适用于有继承关系的类对象,所以普通函数不能说明为虚函数。 静态成员函数不能是虚函数,因为静态成员函数不受限于某个对象,而虚函数的调用必须依附于某一个对象。 内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的。即使虚函数在类的内部定义,编译时,仍将其看作非内联的。,15,构造函数不能是虚函数,因为构造时,对象还是一片未定型的空间。只有在构造完成后,对象才能成为一个类的名副其实的实例。 析构函数可以是虚函数,而且通常声明为虚函数。,16,10.2.4 虚函数表 在C+中:包含虚函数的对象,增加了一个隐含的数据成员vptr ,且是它的第一个数据成员,该数据成员指向一个指针数组vtable ,而指针数组存储对象的虚函数地址.某一个类的虚函数地址表被该类的所有对象共享,甚至有可能两个类共享同一个虚函数地址表。内存开销包括: (1)每一个对象增加了一个额外的数据成员。 (2)每一个类有一个指针表,用于存储该类各虚函数的地址。,17,virtual将一个成员函数说明为虚函数,对于编译器来讲,它的作用是告诉编译器,这个类含有虚函数,对于这个函数不使用静态联编,而是使用动态联编机制. 对于每个包含虚函数的类,编译器都为其创建一个表(称之为VTABLE)。在VTABLE表中放置的是每个类自己的虚函数地址,在每个包含虚函数的对象中放置了一个指针(VPTR),指向VTABLE。通过基类指针调用虚函数时,编译器会在函数调用的地方插入一段特定的代码。这段代码的作用就是得到VPTR,找到VTABLE,并在VTABLE表中找到相应的虚函数地址,然后进行调用,18,课堂练习: 编写交通工具类vehicle(wheels,weight), car(passenger_load ) plane( passenger_load ) boat(passenger_load ) 实现各个类中的刹车brake行为, 使用vehicle指针操作所有对象,察看结果,19,10.2.5 虚析构函数 构造函数的工作过程:它首先调用最上层的基类构造函数,然后调用在继承顺序中的更晚派生的构造函数,如此一块一块地把对象构造起来。 析构函数的工作过程:必须拆卸可能属于某类层次的对象,必须按照构造函数调用相反的顺序,调用所有的析构函数 。 虽然析构函数像构造函数一样,是特殊的成员函数,但析构函数可以是虚的,这是因为这个对象已经知道它是什么类型,而在构造期间则不然,这一点是理解的关键。一旦对象已被构造,它的虚函数表指针vptr就已被初始化了,所以虚函数调用能发生。,20,/new和delete的复习 class A int a; public: A(int n=0) a=n; cout“structor A: “aendl; A( )cout“destructor A: “aendl; ; void f( ) A *pa = new A(10); A a; delete pa; void main() f( ); A *pa = new A(10); ,21,/内存泄露,没有虚析构函数 class A int a; public: A(int n=0) a=n; cout“structor A: “aendl; A( )cout“destructor A: “aendl; ; class B : public A int b; public: B(int n=1) b=n; cout“structor B: “bendl; B( ) cout“destructor B: “bendl; ;,22,void main( ) A *pa = new B(10); delete pa; ,23,/虚析构函数 class A int a; public: A(int n=0) a=n; cout“structor A: “aendl; virtual A()cout“destructor A: “aendl; ; class B : public A int b; public: B(int n=1) b=n; cout“structor B: “bendl; virtual B( ) cout“destructor B: “bendl; ;,24,void main( ) A *pa = new B(10); delete pa; /虚函数析构不管指针类型,只管实际对象,25,10.3 纯虚函数和抽象类,一、纯虚函数(pure virtual function) 定义: 纯虚函数是指被标明为不具体实现的虚拟成员函数。用于实现定义一个基类时,会遇到无法定义基类中虚函数的具体实现,其实现依赖于不同的派生类 格式: virtual 类型 函数名(参数表)=0; 纯虚函数与空的虚函数有着本质的区别.,26,二、抽象类 定义:含有纯虚函数的类. 说明: 抽象类只能用作其它类的基类,不能建立抽象类的对象。因为它的纯虚函数没有定义功能。 抽象类不能用作参数类型、函数的返回类型或显式转换的类型。 可以声明抽象类的指针和引用,通过它们,可以指向并访问派生类对象,从而访问派生类的成员。 若抽象类的派生类中没有给出所有纯虚函数的函数体,这个派生类仍是一个抽象类。,27,例:设计一个抽象类shape,在它下面可以派生出多种具体形状,比如三角形、矩形。 class Shape protected: double x,y; public: void set(double i, double j) x=i; y=j; virtual void area( )=0; /声明纯虚函数 ; class Triangle: public Shape public: void area( ) cout “三角形面积: “ 0.5*x*yendl; ;,28,class Rectangle: public Shape public: void area( )coutset(5.1,10); p-area( ); p= ,29,编程实现: 学校对在册人员进行奖励,依据是业绩分,但是业绩分的计算方法只能对具体人员进行,如学生,教师,行政人员,工人,算法不同。将在册人员类作为一个抽象类,业绩计算方法作为一个纯虚函数,30,基类定义: class Person int MarkAchieve; string Name; public: Person(string name)Name=name; MarkAchieve=0; void SetMark(int mark)MarkAchieve=mark; virtual void CalMark( )=0; void Print( ) coutName“的业绩分为:“ MarkAchieveendl; ;,31,学生派生类定义: class Student:public Person int credit,grade; /学历和成绩 public: Student(string name,int cred,intgrad):Person(name) credit=cred; grade=grad; void CalMark( ) SetMark(credit*grade); ;,32,教师派生类定义: class Teacher : public Person int classhour, studnum; /授课学时和学生人数 public: Teacher(string name,int ch,int sn):Person(name) classhour=ch; studnum=sn; void CalMark( ) SetMark(classhour*studnum*4); ;,33,void main( ) Person *pp; Student s1(“张成“,20,80); Teacher t1(“范英明“,40,80),t2(“李凯“,40,40); pp= ,34,10.4 运算符重载,10.4.1 运算符重载的基本概念 1. 为什么要运算符重载 C+预定义的运算符只是对基本数据类型进行操作,而对于自定义的数据类型比如类、结构体,却没有类似的操作。为了实现对自定义类型的操作,就必须自己编写程序来说明某个运算符作用在这些数据类型上时的操作,这就要引入运算符重载的概念。,35,2. 运算符重载的规则 (1)C+中的运算符除了几个不能重载外,其它的都能重载,而且只能重载已有的运算符,不能创造未知的运算符。不能重载的运算符是:“.”,“*”、“”、“sizeof”和“?:”。 (2)重载以后运算符的优先级和结合性都不能改变,语法结构也不能改变,即单目运算符只能重载为单目运算符,多目运算符只能重载为多目运算符。 (3)运算符重载以后的功能应与原有功能类似,含义必须清楚,不能有二义性。,36,10.4.2 运算符重载为类的成员函数 运算符的重载形式有两种,一种是重载为类的成员函数,一种是重载为类的友元函数。 将运算符重载为它将要操作的类的成员函数,称为成员运算符函数。实际使用时,总是通过该类的某个对象访问重载的运算符。 在类内声明的一般形式为: 函数类型 operator 运算符(参数表); 在类外定义的一般形式为: 函数类型 类名operator运算符(参数表) 函数体 ,37,10.4.3 各种运算符的重载 1. 双目运算符重载为成员运算符函数 双目运算符重载为成员函数时,左操作数是访问该重载运算符的对象本身的数据,由this指针指出,右操作数通过成员运算符函数的参数指出。例如: class X public: int operator+(X ,38,2. 单目运算符重载为成员函数 运算符+和-有前置和后置两种形式,要使用operator+()或operator-()来重载前置运算符,使用operator+(int)或operator-(int)来重载后置运算符,调用时,参数int被传递给值0。 显式和隐式两种调用方法: (1)显式调用:对象名.operator运算符号() 例如:obja.opreator+(); (2)隐式调用:重载的运算符号 对象名 例如:+obja;,39,3. 赋值运算符“=”的重载 (1)对于任何一个类,如果没有用户自定义的赋值运算符函数,系统会自动地为其生成一个缺省的赋值运算符函数,以完成数据成员之间的逐位拷贝。 (2)通常情况下,缺省的赋值

温馨提示

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

评论

0/150

提交评论