《多态和虚函数》PPT课件.ppt_第1页
《多态和虚函数》PPT课件.ppt_第2页
《多态和虚函数》PPT课件.ppt_第3页
《多态和虚函数》PPT课件.ppt_第4页
《多态和虚函数》PPT课件.ppt_第5页
已阅读5页,还剩64页未读 继续免费阅读

下载本文档

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

文档简介

北京亚嵌教育-中国嵌入式技术的黄埔军校 多态和虚函数 北京亚嵌教育研究中心 2010 AKAE 多态性是面向对象程序设计的重要特征 之一,多态性机制: 增加了面向对象软件系统的灵活性 减少了冗余信息 提高了软件的可重用性和可扩充性 多态性是面向对象的一个重要特征。什么是多态性? 以下为结构化编程中的一个例子: void fuite_eat(int objFruit) switch(objFruit) case 0: /apple apple_eat(); /printf(“apple eat“); break; case 1: /orange orange_eat(); /printf(“orange eat“); break; case 2: /peal peal_eat(); /printf(“peal eat“); break; l上面的例子中以水果为例,不同的 水果的吃法可能各不相同,这样要根 据不同的水果类型来判断调用特定的 水果的吃的方法。 l在程序编译时系统预先绑定了各种 水果吃的方法,这就是“前期绑定”。 l但当增加一种水果时,不得不更改 上述程序,新增加一个入口,并调用 新水果的吃法。 面向对象技术中的多态性运用“ 后期绑定”技术解决此问题。 可以在基类中定义一个虚函数,然 后在派生类中覆盖它,当调用此方法时, 系统会根据对象的类型而决定调用哪一个 对象的方法(即:不同对象收到相同消息 时,产生不同的动作 )。 如: class fruit virtual void eat()=0; class apple:public fruit void eat() printf(“apple eat“); class orange:public fruit void eat() printf(“orange eat“); void fruite_eat(fruit * f) f-eat(); 编译器发现一个类中有被声明为virtual的函数 ,就会为它建一个虚函数表,即 VTABLE。 VTABLE实际上是一个函数指针的数组,每个虚 函数占用这个数组的一个slot。一个类只有一个 VTABLE,不管它有多少个实例。 派生类有自己的VTABLE,但是,派生类的 VTABLE与基类的VTABLE有相同的函数排列顺 序,同名的虚函数被放在两个数组的相同位置上 。 在创建类实例的时候,编译器还会在每个实例的 内存布局中增加一个vptr字段,该字段指向本类 的VTABLE。 上例中:系统在得到对象的指针后会查找 VTABLE,找到方法的函数指针,然后调用该 方法。如果此方法未被派生类实现,则系统会 调用其基类的方法。 函数地址的后期绑定在面向对象的编程语言中 是非常重要的,在java中对类中所有函数都进 行了后期绑定,但C+,则是让程序开发者决 定对哪一个函数进行后期绑定。 为多态性行为提供后期绑定是要付出代价的, 因为在类的实现时VTABLE要进行初始化且在 调用虚函数时必须在运行时查找虚函数表。 1 多态性概述 多态性含义多态性含义: : 指不同对象收到相同的消息时,产 生不同的动作。 体现在程序中为体现在程序中为: : 多态性是指用一个名字定义不同的 函数,这些函数执行不同但又类似的操 作,从而可以使用相同的调用方式来调 用这些具有不同功能的同名函数。 1.1 1.1 多态的分类多态的分类 C+中的多态性可以分为四类: 参数多态:由类模板实例化各个类都有的具有相同 的操作,而操作对象的类型却各不相同 包含多态:主要通过虚函数来实现。强调不同类中 的同名成员函数的多态行为 强制多态:即,将一个变元的类型实例化,比如加 法运算时候浮点数与整数的强制转换,即运算符重 新定义(重载)。 重载多态 前面两种统称为通用多态,而后面两种 统称为专用多态。 1.2 1.2 多态的实现多态的实现 多态从实现的角度分为两类: 编译时的多态: 是通过静态联编来实现的。静态联编就是 在编译阶段完成的联编。编译时多态性主要是 通过函数重载函数重载和运算符重载实现的。 运行时的多态 是用动态联编实现的。动态联编是运行阶 段完成的联编。运行时多态性主要是通过虚函 数来实现的。 2 运算符重载 什么是运算符重载? 重载,即重新赋予新的含义。 函数重载就是对一个已有的函数赋予新的含义 ,使之实现新功能。 运算符也可以重载,对C+已提供的运算符进 行重载,赋予它们新的含义,使之一名多用。 譬如,用“+”号如何进行两个复数的相加? C+中不能直接用运算符“+”对复数进行相加 运算。 可以通过定义一个专门的函数来实现复数相加 。 例1 通过函数来实现复数相加。 #include using namespace std; class Complex /定义Complex类 public: Complex( )real=0;imag=0; /定义构造函数 Complex(double r,double i) /构造函数重载 real=r;imag=i; Complex complex_add(Complex /声明复数相加函数 void display( ); /声明输出函数 private: double real; /实部 double imag; /虚部 ; Complex Complexcomplex_add(Complex this-imag= this-imag+c2.imag; return *this; void Complexdisplay( ) /定义输出函数 coutreal+c2.real,this-real就是c1.real。 前面已说明,在将运算符函数重载为成员 函数后,如果出现含该运算符的表达式,如 c1+c2,编译系统把它解释为: c1.operator+(c2) 运算符重载函数的返回值是Complex类型 ,返回值是复数c1和c2之和 (Complex(c1.real + c2.real,c1.imag+c2.imag) 。 运算符重载函数除了可以作为类的成员函数外,运算符重载函数除了可以作为类的成员函数外, 还可以是非成员函数。还可以是非成员函数。 例3 将运算符“+”重载为适用于复数加法,重载函数不作 为成员函数,而放在类外,作为Complex类的友元函数。 class Complex public: Complex( ) real=0;imag=0; Complex(double r,double i) real=r;imag=i; friend Complex operator + (Complex /重载函数作为友元函数 void display( ); private: double real; double imag; ; Complex operator + (Complex void Complexdisplay( ) cout” ,用于两个字符串的等于、小于和大于的比 较运算。分几步来介绍编程过程: (1) 先建立一个String类: #include using namespace std; class String public: String( )p=NULL; /默认构造函数 String(char *str); /构造函数 void display( ); private: char *p; /字符型指针,用于指向字符串 ; StringString(char *str) /定义构造函数 p=str; /使p指向实参字符串 void Stringdisplay( ) /输出p所指向的字符串 cout”。程序如下: #include #include using namespace std; class String public: String( )p=NULL; String(char *str); friend bool operator(String /声明运算符函数为友元函数 void display( ); private: char *p; /字符型指针,用于指向字符串 ; StringString(char *str) p=str; void Stringdisplay( ) /输出p所指向的字符串 coutoperator(String else return false; int main( ) String string1(Hello),string2(Book); coutstring2) (String friend bool operator(String else return false; bool operatorstring2) using namespace std; class String public: String( )p=NULL; String(char *str); friend bool operator(String friend bool operator(String else return false; bool operator(string1,string2)=1) string1.display( );cout; string2.display( ); else if (operatorBook Book class A public: void show() coutshow(); pc= pc-show(); return 0; ?运行结果:AA 预想结果:AB 问题:指向基类对象的指针 可以指向它的公有派生类对 象。但这个对象指针调用同名 但不同级的成员函数时会有 问题。 #include class base int a,b; public: base(int x,int y) a=x;b=y; void show() coutshow (); pc= pc-show (); 运行结果: base- 60 60 base- 10 20 ?没有指向派生类 例10 虚函数的引例2 错误的原因:C+的静态联编 机制。静态联编首先将指向 基类对象的Pc与基类的成员 函数show()连接在一起,以 后不管pc再指向哪个对象, pc-show调用的总是基类 的成员函数show(). void main() base mb(60,60),*pc; derived mc(10,20,30); pc= pc-show (); pc= pc-show (); void main() / pc= pc-show (); pc= pc-show (); mc.show(); (derived *)pc)-show(); 更改获得预期 结果的方法 base- 60 60 base- 10 20 derived- c= 30 derived- c= 30 但这两方法都没有实现一种动态的绑定,即但这两方法都没有实现一种动态的绑定,即 当指针指向不同的对象时执行不同的操作。当指针指向不同的对象时执行不同的操作。 解决方法:虚函数解决方法:虚函数 3.2 3.2 虚函数的作用和定义虚函数的作用和定义 1.1.虚函数的作用虚函数的作用 虚函数同派生类的结合可使C+支持运 行时的多态性,实现了在基类定义派生 类所拥有的通用接口,而在派生类定义 具体的实现方法,即常说的“同一接口, 多种方法”,它帮助程序员处理越来越复 杂的程序。 例11 虚函数的作用。 #include class Base public: Base(int x,int y) a=x; b=y; virtual void show() /定义虚函数show() coutshow(); /调用基类Base的show()版本 pc= pc-show(); /调用派生类Derived的show()版本 程序运行结果如下: Base- 60 60 Derived- 30 结果正确 关键字virtual指示c+编译器,函 数调用pc-show()在运行时确定调 用的函数,即对调用进行动态联编 程序在运行时根据指针pc所指向的 实际对象调用该对象的成员函数 2. 2. 虚函数的定义虚函数的定义 定义虚函数的方法如下: virtual 函数类型 函数名(形参表) / 函数体 基类中声明为虚函数的成员函数在派生类中被基类中声明为虚函数的成员函数在派生类中被 派生类派生类重定义重定义,并要求函数原型(包括返回类,并要求函数原型(包括返回类 型、函数名、参数个数、参数类型的顺序)都型、函数名、参数个数、参数类型的顺序)都 必须与基类中的原型完全相同。必须与基类中的原型完全相同。 对虚函数的定义的几点说明: 通过定义虚函数使用C+提供的多态性机制 时,派生类应从其基类公有派生公有派生 必须首先在基类中定义为虚函数 C+规定,当一个成员函数被声明为虚函数当一个成员函数被声明为虚函数 后,后,其派生类中的同名函数都自动成为虚函其派生类中的同名函数都自动成为虚函 数数。因此,派生类中的virtual可写可不写, 为避免混乱,最好在派生类的虚函数进行重 新定义时加上关键字virtual。 一般是先定义基类指针,然后通过基类指针一般是先定义基类指针,然后通过基类指针 指向派生类,访问虚函数获取运行时多态性指向派生类,访问虚函数获取运行时多态性 。 一个虚函数无论被公有继承多少次,仍保持 其虚函数特性 虚函数必须是类的成员函数,不能是友员函 数,也不能是静态成员函数。 内联函数不能是虚函数,因为内联函数是在 编译地确定位置。虚函数虽然定义在类内部 ,但编译时仍将其视为非内联。 构造函数不能是虚函数,因为虚函数作为运 行过程中多态的基础,主要是针对对象的, 而构造函数是在对象产生之前运行的,因此 虚构造函数无意义。 析构函数可以是虚函数。 3.33.3虚析构函数虚析构函数 析构函数前面加上关键字virtual进行说明,称该析 构函数为虚析构函数。例如: class B virtual B(); /虚析构函数 ; 基类的析构函数被说明为虚析构函数,则其派生类中的析构 函数也是虚析构函数。 说明虚析构函数的目的在于在使用delete运算符删除一个对 象时,能保析构函数被正确地执行。因为设置虚析构函数后 ,可以采用动态联编方式选择析构函数。 #include class A public: virtual A() cout class Circle public: void setr(int x) r=x; virtual void show()=0; / 纯虚函数 protected: int r; ; class Area:public Circle public: void show() coutshow(); ptr= ptr-show(); 4.2 4.2 抽象类抽象类 一般地,类用于定义对象。而有一些类不用来生 成对象。定义这些类的惟一目的是用它作为基类 去建立派生类。它们作为一种基本类型提供给用 户,用户在这个基础上根据自己的需要定义出功 能各异的派生类。用这些派生类去建立对象。 这种不用来定义对象而只作为一种基本类型用作 继承的类,称为抽象类(abstract class),由于它 常用作基类,通常称为抽象基类(abstract b

温馨提示

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

评论

0/150

提交评论