C程序设计--对象分册(第3章).ppt_第1页
C程序设计--对象分册(第3章).ppt_第2页
C程序设计--对象分册(第3章).ppt_第3页
C程序设计--对象分册(第3章).ppt_第4页
C程序设计--对象分册(第3章).ppt_第5页
已阅读5页,还剩32页未读 继续免费阅读

下载本文档

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

文档简介

1,第3章多态性,本章学习重点掌握内容: 多态的概念和作用,多态的实现方法 常见运算符的重载 静态联编和动态联编 虚函数、纯虚函数和抽象基类的概念和用法 虚析构函数的概念和作用,虚析构函数的用法,2,第3章多态性,3.1多态性的概念 3.2 运算符重载 3.3联编和虚函数 3.4 纯虚函数和抽象类 3.5 综合应用实例,3,3.1多态性的概念,多态性就是一个事物多种形态,就是同一符号或者名字在不同情况下具有不同解释的现象。 多态性有两种表现形式: 一种是不同的对象在收到相同的消息时,产生不同的动作; 另一种是同一对象收到相同的消息却产生不同的函数调用。,4,多态的两种实现方式,两种表现形式分别叫做:编译时多态和运行时多态。 编译时多态也叫静态多态性,属于早期绑定,在编译时就实现了绑定,它是静态联编的; 实现 方式重载,包括函数重载。操作符重载 运行时多态也叫动态多态性,属于晚期绑定,在编译时还无法确定绑定对象,只有在运行时才能够实现绑定,它是动态联编的。实现方式:虚函数,5,3.2.1 运算符重载概述,复数类Complex class Complex public: Complex () real=image=0; Complex (double r, double i) real = r, image = i; void Print(); private: double real, image; ;,void Complex:Print() if(image0) coutreal -imagei; else coutreal+imagei; ,6,3.2.1 运算符重载概述,complex c1(2.0, 3.0), c2(4.0, -2.0), c3; c3=c1+c2; 编写程序来实现“+”运算符来作用于complex类的对象,这就是运算符的重载。 运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时,导致不同类型的行为。,7,3.2.1 运算符重载概述,(1)一般来说,不改变运算符原有含义,只让它能针对新类型数据的实际需要,对原有运算符进行适当的改造。例如,重载“+”运算符后,它的功能还是进行加法运算。 (2)重载运算符时,不能改变运算符原有的优先级别,也不能改变运算符需要的操作数的数目。重载之后运算符的优先级和结合性都不会改变。 (3)不能创建新的运算符,只能重载c+中已有的运算符。 (4)有些运算符不能进行重载。如:“.”类成员运算符、“*”类指向运算符、“:”类作用域运算符、“?:”条件运算符及“sizeof”求字节数运算符。,8,3.2.2 运算符重载的实现,运算符重载的本质就是函数重载。属于静态多态性 首先把指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参,然后根据实参的类型来确定需要调用的函数. 这个过程是在编译过程中完成的。 运算符重载形式有两种: 类的成员函数、友元函数。,9,语法形式如下: 函数类型 operator 运算符(形参表) 函数体; 重载一元运算符没有参数;重载二元运算符只有一个参数。 一般基于某个对象调用成员函数,这个对象是一个隐含的操作数,就是被调用的运算符函数的一个操作数,而且是第一操作数 有了运算符重载以后,当程序中出现表达式:“ oCCounter2 = oCCounter2 + oCCounter1;“的时候编译程序将其解释为:“oCCounter2 = oCCounter2.operator+( oCCounter1 )“ 成员函数的调用只能够通过成员函数所属的类的对象来调用,也就是说被重载的运算符的第一操作数的类型是确定的,是隐含的,不可改变。,运算符重载为类的成员函数,10,运算符重载为类的友元函数,语法形式如下: friend 函数类型 operator 运算符(形参表) 函数体; 不是成员函数,也没有this指针,用友元函数重载二元运算符(双目运算符)时,要有两个参数;重载一元运算符(单目运算符)时,要有一个参数。,11,成员函数与友元函数重载区别,当运算符重载为类的成员函数时,函数的参数个数比原来的参与运算的运算数个数要少一个(后缀+、-除外 ). 当运算符重载为类的友元函数时,参数个数与原运算数的个数相同。,12,单目和双目运算符重载的重载,一般来讲,单目运算符最好重载为成员函数,而双目运算符则最好重载为友元函数。 1 单目运算符重载 可重载为没有参数的成员函数或者带有一个参数的非成员函数(友元函数),其参数必须是用户定义类型的对象或者是对该对象的引用,13,单目运算符重载,单目运算符+和-,重载时,由于有前缀和后缀的差别,其参数个数遵照以下格式规定: operator +( );/前缀运算符重载 operator +(int); /后缀运算符重载 operator -( ); /前缀运算符重载 operator -(int); /后缀运算符重载,前缀和后缀重载的区分.,14,双目运算符重载,可以重载为带一个参数的成员函数或者带两个参数的友元函数。 问题: “ CCounter operator+( CCounter ob );“ “oCCounter2 = oCCounter1 + 20;” /正确 “ oCCounter2 = 20 + oCCounter1;” /错误 解决 使用友元重载 “friend CCounter operator+( CCounter ob, int );“ “friend CCounter operator+( int, CCounter ob );“,15,3.2.3 双目运算符重载,【例3.1】定义一个复数类,重载“+”运算符为复数类的成员函数,使这个运算符能直接完成两个复数的加法运算,以及一个复数与一个实数的加法运算。 【例3.2】重载“+”运算符为复数类的友元函数,使这个运算符能直接完成两个复数的加法运算,以及一个复数与一个实数的加法运算。 【例3.3】日期类date中采用友元形式重载“+”运算符,实现日期加上一个天数,得到新日期。 【例3.6】重载单目运算符“+”。,16,3.2.4 赋值运算符重载,在C+中有两种类型的赋值运算符:一类是“+=”和“-=”等先计算后赋值的运算符,另一类是“=”即直接赋值的运算符。 1运算符“+=”和“- =”的重载 【例3.4】 实现复数类“+=”和“-=”的重载。,17,3.2.4 赋值运算符重载,正常情况下,系统会为每一个类自动生成一个默认的完成上述功能的赋值运算符。这个赋值运算符完成的是“复制赋值”的功能。 但这种“复制赋值”是一种“浅复制”,即它将对象数据成员“逐位”(bit)进行复制赋值。(拷贝构造函数) 成员变量包含指针时,是非常危险的,所以需要重载赋值运算。同样也需要重载拷贝构造函数。 想想结构(struct)的赋值 注意:赋值运算系统自动生成,其他操作则不自动生成 例3.5,18,3.2.6 下标运算符重载,下标运算符“ ”通常用于在数组中标识数组元素的位置,下标运算符重载可以实现数组数据的赋值和取值。下标运算符重载函数只能作为类的成员函数,不能作为类的友元函数。 下标运算符“ ”函数重载的一般形式为: 函数类型 operator (形参表); 其中形参表为该重载函数的参数列表。重载下标运算符只能且必须带一个参数,该参数给出下标的值。 注意下标操作符需要出现在赋值操作符的左右两边,为了能在左边出现,返回值必须是引用。 以字符类为例说明。,19,3.2.7 关系运算符重载,关系运算符也可以被重载,例如定义一个日期类date,重载运算符“= =”和“”,用于两个日期的等于和小于的比较运算。,20,3.2.8 类型转换运算符重载,类型转换函是一种特殊类型的类成员函数 它定义了一个由用户定 义的转换 以便把一个类对象转换成某种其他的类型 类型转换运算符重载函数的格式如下: operator 类型名() 函数体 类型转换运算符重载函数没有返回类型,因为类型名就代表了它的返回类型,而且也没有任何参数。在调用过程中要带一个对象实参。 可以同时定义多个类型转换函数,即类转换成多种类型 例子实现复数类转换成double型和字符类转换成字符指针的例子,21,3.3 联编和虚函数,3.3.1 静态联编和动态联编 面向对象的多态性从实现的角度来讲,可以分为静态多态性和动态多态性两种。在源程序编译的时候就能确定具有多态性的语句调用哪个函数,称为静态联编。对于重载函数的调用就是在编译的时候确定具体调用哪个函数,所以是属于静态联编。 编译程序在编译阶段并不能确切知道将要调用的函数,只有在程序执行时才能确定将要调用的函数,为此要确切知道该调用的函数,要求联编工作要在程序运行时进行,这种在程序运行时进行联编工作被称为动态联编,或称动态束定,又叫晚期联编。,22,3.3.2 虚函数,在C+中,动态联编是通过虚函数来实现的。 虚函数的本质是将派生类类型的指针赋给基类类型的指针,虚函数被调用时会自动判断调用对象的类型,从而做出相应的响应。 【例3.10】 #include class CPerson public: void PrintInfo() cout“Personn“; ;,2019/8/2,23,class CWorker: public CPerson private: int kindofwork; public: void PrintInfo () cout“Workern“; ;,class CTeacher: public CPerson private: int subject; public: void PrintInfo () cout“Teachern“; ;,void main() CWorker w; CTeacher t; CPerson* p; p = ,程序的执行结果为: Person Person Person,分析实际上,无论引用基类对象还是派生类对象,函数内调用的都是基类的PrintInfo ( )成员函数。 通过基类的引用去引用派生类对象,只能看到派生类从基类中继承而来的部分。 这是由于C+的静态联编机制造成的。它首先将指向基类的指针与基类成员函数PrintInfo ()连接在一起。这样,不管p指向哪个对象,p- PrintInfo ( )调用的总是基类的成员函数PrintInfo ( )。,24,虚函数,C+提供了虚函数(virtual function)机制解决上述问题, 对于上面的例子,把基类的成员函数定义为虚函数,分析运行结果。 。 通过虚函数机制,实现了动态联编,即如果是虚函数,在运行时根据指针或引用实际指向的对象,调用对象的函数,普通函数只能根据指针或引用的类型。(上述例子改成函数形式,效果更明显) 一旦基类的成员函数定义为虚函数,则其派生类的同名成员函数(原型一致)不管前面是否加关键字virtual,同样具有虚特性,同样是虚函数。,25,几条重要规则-1,、基类中把成员函数定义为虚函数后,派生类和基类的对应成员函数不仅名字相同,而且返回类型、参数个数和类型也必须相同。原型不一致,即使加上了关键字virtual,也不会进行滞后联编。 、类的成员函数才能说明为虚函数。 、静态成员函数不能是虚函数,静态成员函数是不受某个对象的限制,它属于类。,26,几条重要规则-2,、内联( inline )函数不是虚函数,内联函数不能在运行时动态确定位置,它在编译时采用插入函数体的方式处理。 、构造函数不虚函数,构造对象的时候,对象还是一片未定型的空间,只有对象构造完成后,对象才是具体类的实例。 、析构函数可以是虚函数,而且通常将析构函数声明为虚函数。 7.运行 时多态,必须通过指针和引用来实现,27,28,虚函数与重载函数的比较,(1)重载函数要求函数有相同的函数名,并有不同的参数序列;而虚函数则要求这三项(函数名、返回值类型和参数序列)完全相同; (2)重载函数是同一层次上的同名函数问题,而虚函数是不同层次上的同名函数问题。 (3)如果是不同层次上的同名函数(三同),且不是虚函数,则会产生同名覆盖 (2)重载函数可以是成员函数或友员函数,而虚函数只能是成员函数; (3)重载函数的调用是以所传递参数序列的差别作为调用不同函数的依据;虚函数是根据对象的不同去调用不同类的虚函数; (4)虚函数在运行时表现出多态功能,C+的精髓;而重载函数则在编译时表现出多态性。,29,关于虚函数有以下几点说明,当基类中把成员函数定义为虚函数后,派生类和基类的对应成员函数不仅名字相同,而且返回类型、参数个数和类型也必须相同。 基类中虚函数前的virtual不能省略,派生类中的虚函数的virtual关键字可以省略 运行时多态须通过基类对象的引用或基类对象的指针调用虚函数才能实现。 虚函数必须是类的成员函数,不能是友员函数,不能是静态成员函数。 构造函数不能定义为虚函数,一般将析构函数定义为虚函数。,30,3.3.4 虚析构函数,不能声明虚构造函数,因为在构造函数执行时,对象还没有完全构造好,不能按虚函数方式进行调用。 可声明虚析构函数,如果用基类指针指向一个new生成的派生类对象,通过delete作用于基类指针删除派生类对象时,有以下两种情况: (1)基类析构函数不为虚析构函数,会调用基类的析构函数,派生类的析构函数不会被调用,派生类对象中派生的那部分内存空间无法析构释放。 (2)如果基类析构函数为虚析构函数,释放基类指针的时候会调用基类和派生类中的所有析构函数,派生类对象中所有的内存空间都将被释放,包括继承基类的部分。 所以C+中的析构函数通常是虚析构函数。,31,3.3.4 虚析构函数,【例3.12】虚析构函数的用法和作用示例。 #include class Base1 public: Base1() cout “Base1()n“; ; class Derived1 : public Base1 public: Derived1() cout “Derived1()n“; ; class Base2 public: virtual Base2() cout “Base2()n“; ;,2019/8/2,32,class Derived2 : public Base2 public: Derived2() cout “Derived2()n“; ; void main() Base1* bp = new Derived1; delete bp; Base2* b2p = new Derived2; delete b2p; 运行结果: Base1() Derived2() Base2(),33,3.4 纯虚函数和抽象类,在许多情况下,在基类中不能给出有意义的虚函数定义, 可以在基类person中加一个displaySalary函数,并声明为虚函数: virtual void displaySalary (int m,int s ) return 0; virtual void displaySalary (int m,int s ) =0; 这就将void displaySalary (int m,int s )声明为一个纯虚函数(pure virtual function),把它的具体定义留给派生类来做。,纯虚函数,在C+中,对于那些在基类中不需要定义具体的行为的函数,可以定义为纯虚函数。 声明纯虚函数的一般形式是 class 类名 virtual 类型 函数名(参数表)=0; /纯虚函数 . ; 如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数。,34,35,3.4.2 抽象类,如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。 抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类中的纯虚函数可能是在抽象类中定义的,也可能是从它的抽象基类中继承下来且重定义的。 抽象类有一个重要特点,即抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。 可使用指向抽象类的指针支持运行时多态性。 抽象类定义的一般形式是: class 类名 public: virtual 函数名(参数表) = 0;

温馨提示

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

评论

0/150

提交评论