C程序设计语言揣锦华第8章多态性.ppt_第1页
C程序设计语言揣锦华第8章多态性.ppt_第2页
C程序设计语言揣锦华第8章多态性.ppt_第3页
C程序设计语言揣锦华第8章多态性.ppt_第4页
C程序设计语言揣锦华第8章多态性.ppt_第5页
已阅读5页,还剩82页未读 继续免费阅读

下载本文档

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

文档简介

第8章 多态性 第8章 多态性 8.1 多态性概述 8.2 运算符重载 8.3 虚函数 8.4 抽象类 第8章 多态性 8.1 多态性概述 所谓多态性是指同一个接口可以通过多种方法调 用,如图8-1所示。通俗地说,多态性是指用一个相同 的名字定义不同的函数,这些函数的执行过程不同, 但是有相似的操作,即用同样的接口访问不同的函数 。比如,一个对象中有很多求两个数中最大值的行为 ,虽然可以针对不同的数据类型,写很多不同名称的 函数来实现,但事实上,它们的功能几乎完全相同。 这时,就可以利用多态的特征,用统一的标识来完成 这些功能。 第8章 多态性 图8-1 多态性为用户提供单一接口示意图 第8章 多态性 面向对象的多态性从实现的角度来讲,可以分为 静态多态性和动态多态性两种。静态多态性是在编译 的过程中确定同名操作的具体操作对象的,而动态多 态性则是在程序运行 过程中动态地确定操作所针对的具体对象的。这 种确定操作具体对象的过程就是联编(binding),也称为 绑定。联编是指计算机程序自身彼此关联的过程。也 就是把一个标识符名和一个存储地址联系在一起的过 程。用面向对象的术语讲,就是把一条消息和一个对 象的方法相结合的过程。 第8章 多态性 所谓消息,是指对类的成员函数的调用。不同的 方法是指不同的实现,也就是调用了不同的函数。按 照联编进行阶段的不同,联编方法可以分为两种:静 态联编和动态联编。这两种联编过程分别对应着多态 的两种实现方式。联编工作在编译连接阶段完成的情 况称为静态联编。在编译、连接过程中,系统就可以 根据类型匹配等特征确定程序中操作调用与执行该操 作的代码的关系,即确定某一个同名标识到底是要调 用哪一段程序代码。函数重载和运算符重载就属于静 态多态性。 第8章 多态性 和静态联编相对应,如果联编工作在程序运行阶段 完成,则称为动态联编。在编译、连接过程中无法解决 的联编问题,要等到程序开始运行之后再来确定。例如 ,本章将要介绍的虚函数就是通过动态联编完成的。 函数重载在函数及类的章节中曾做过详细的讨论, 所以在本章中,静态多态性主要介绍运算符重载;对于 动态多态性,将对虚函数作详细介绍。 第8章 多态性 8.2 运算符重载 C+中预定义的运算符的操作对象只能是基本数据 类型。实际上,对于很多用户自定义的类型(如类),也 需要有类似的运算操作。例如,下面的程序声明了一个 点类point。 classpoint/point类声明 private: intx,y; 第8章 多态性 public:/构造函数 point(intxx=0,intyy=0)x=xx;y=yy; intget_x();/显示x值 intget_y();/显示y值 /. ; 第8章 多态性 于是我们可以这样声明点类的对象: pointp1(1,1),p2(3,3) 如果我们需要对p1和p2进行加法运算,该如何实 现呢?我们当然希望能使用“+”运算符,写出表达式 “p1+p2”,但是编译的时候却会出错,因为编译器不知 道该如何完成这个加法。这时候,我们就需要自己编 写程序来说明“+”在作用于point类对象时,该实现什么 样的功能,这就是运算符重载。运算符重载是对已有 的运算符赋予多重含义,使同一个运算符作用于不同 类型的数据时,导致不同类型的行为。 第8章 多态性 在运算符重载的实现过程中,首先把指定的运算表达 式转化为对运算符函数的调用,运算对象转化为运算 符函数的实参,然后,根据实参的类型来确定需要调 用的函数。这个过程是在编译过程中完成的。 第8章 多态性 8.2.1 运算符重载的规则 运算符是在C+系统内部定义的,它们具有特定的 语法规则,如参数说明、运算顺序、优先级别等。因 此,运算符重载时必须要遵守一定的规则。 C+中的运算符除了少数几个(类属关系运算符 “.”、作用域分辨符“:”、成员指针运算符“*”、sizeof运 算符和三目运算符“?:”)之外,全部可以重载,而 且只能重载C+中已有的运算符,不能臆造新的运算 符。 第8章 多态性 重载之后运算符的优先级和结合性都不能改变, 也不能改变运算符的语法结构,即单目运算符只能重 载为单目运算符,双目运算符只能重载为双目运算符 。 运算符重载后的功能应当与原有功能相类似。 重载运算符含义必须清楚,不能有二义性。 运算符的重载形式有两种:重载为类的成员函数和 重载为类的友元函数。 第8章 多态性 运算符重载为类的成员函数的一般语法形式如下: operator(形参表) 函数体; 运算符重载为类的友元函数的一般语法形式如下: friendoperator(形参表) 函数体; 第8章 多态性 其中: 函数类型指定了重载运算符的返回值类型,也就 是运算结果类型。 operator是定义运算符重载函数的关键字。 运算符是要重载的运算符名称。 形参表给出重载运算符所需要的参数和类型。 friend是对于运算符重载为友元函数时,在函数 类型说明之前使用的关键字。 第8章 多态性 特别需要注意的是,当运算符重载为类的成员函 数时,函数的参数个数比原来的操作数个数要少一个( 后置“+”、“-”除外);当重载为类的友元函数时,参 数个数与原操作数的个数相同。原因是重载为类的成 员函数时,如果某个对象使用重载了的成员函数,自 身的数据可以直接访问,就不需要再放在参数表中进 行传递,少了的操作数就是该对象本身。 第8章 多态性 8.2.2 运算符重载为成员函数 运算符重载实质上就是函数重载,当运算符重载 为成员函数之后,它就可以自由地访问本类的数据成 员了。实际使用时,总是通过该类的某个对象来访问 重载的运算符。如果是双目运算符,一个操作数是对 象本身的数据,由this指针指出,另一个操作数则需要 通过运算符重载函数的参数表来传递;如果是单目运 算符,操作数由对象的this指针给出,就不再需要任何 参数。下面分别介绍这两种情况。 第8章 多态性 1双目运算:oprdlBoprd2 对于双目运算符B,如果要重载B为类的成员函数 ,使之能够实现表达式oprdlBoprd2(其中oprdl为A类的 对象),则应当把B重载为A类的成员函数,该函数只有 一个形参,形参的类型是oprd2所属的类型。经过重载 之后,表达式oprdlBoprd2就相当于函数调用 oprdl.operatorB(oprd2)。 第8章 多态性 2单目运算 1)前置单目运算:Uoprd 对于前置单目运算符U,如“-”(负号)、“+”等,如 果要重载U为类的成员函数,用来实现表达式Uoprd(其 中oprd为A类的对象),则U应当重载为A类的成员函数 ,函数没有形参。经过重载之后,表达式Uoprd相当于 函数调用oprd.operatorU()。 例如,前置单目运算符“+”重载的语法形式如下: operator+(); 使用前置单目运算符“+”的语法形式如下: +; 第8章 多态性 2) 后置单目运算:oprdV 再来看后置运算符V,如“+”和“-”,如果要将它 们重载为类的成员函数,用来实现表达式oprd+或 oprd-(其中oprd为A类的对象),那么运算符就应当重载 为A类的成员函数,这时函数要带有一个整型(int)形参 。重载之后,表达式oprd+和oprd-就相当于函数调用 oprd.operator+(0)和oprd.operator-(0)。 例如,后置单目运算符“+”重载的语法形式如下 : operator+(int); 使用后置单目运算符“+”的语法形式如下: +; 第8章 多态性 【例8-1】双目运算符重载为成员函数例题。 本例题重载二维点point加减法运算(关于二维点 point类的定义在前面章节中已介绍过),将一个双目运 算符重载为成员函数。point的加减法是x和y分别相加 减,运算符的两个操作数都是point类的对象,因此, 可以把“+”、“-”运算符重载为point类的成员函数,重载 函数只有一个形参,类型同样也是point类对象。 第8章 多态性 #include classpoint private: floatx,y; public: point(floatxx=0,floatyy=0)x=xx;y=yy; floatget_x()returnx; floatget_y()returny; 第8章 多态性 pointoperator+(pointp1);/重载运算符“+” pointoperator-(pointp1);/和“-”为成员函数 ; pointpoint:operator+(pointq) returnpoint(x+q.x,y+q.y); pointpoint:operator-(pointq) returnpoint(x-q.x,y-q.y); voidmain() 第8章 多态性 pointp1(3,3),p2(2,2),p3,p4;/声明point类的对象 p3=p1+p2;/两点相加 p4=p1-p2;/两点相减 cout classpoint private: floatx,y; public: point(floatxx=0,floatyy=0)x=xx;y=yy; floatget_x()returnx; floatget_y()returny; 第8章 多态性 pointoperator+();/重载前置运算符“+” pointoperator-();/重载前置运算符“-” ; pointpoint:operator+() if(x0)-x; if(y0)-y; return*this; voidmain() pointp1(10,10),p2(200,200); /声明point类的对象 for(inti=0;i”。 第8章 多态性 1双目运算:oprdlBoprd2 对于双目运算符B,如果oprdl为A类的对象,则应 当把B重载为A类的友元函数,该函数有两个形参,其 中一个形参的类型是A类。经过重载之后,表达式 oprdlBoprd2就相当于函数调用operatorB(oprdl,oprd2) 。 第8章 多态性 2单目运算 1)前置单目运算:Uoprd 对于前置单目运算符U,如“-”(负号)等,如果要实 现表达式Uoprd(其中oprd为A类的对象),则U可以重载 为A类的友元函数,函数的形参为A类的对象。经过重 载之后,表达式Uoprd相当于函数调用operatorU(oprd) 。 第8章 多态性 2)后置单目运算:oprdV 对于后置运算符V,如“+”和“-”,如果要实现表 达式oprd+或oprd-(其中oprd为A类的对象),那么 运算符就可以重载为A类的友元函数,这时函数的形参 有两个,一个是A类的对象oprd,另一个是整型(int)形 参。重载之后,表达式oprd+和oprd-就相当于函数调 用operator+(oprd,0)和operator-(oprd,0)。 第8章 多态性 【例8-3】双目运算符重载为友元重载例题。 本例题用运算符重载为友元函数的方法重做两点 加减法运算。 #include classpoint private: floatx,y; public: 第8章 多态性 point(floatxx=0,floatyy=0)x=xx;y=yy; floatget_x()returnx; floatget_y()returny; friendpointoperator+(pointp1,pointp2);/重载运算符 “+” friendpointoperator-(pointp1,pointp2);/和“-”为友元函 数 ; pointoperator+(pointp1,pointp2) 第8章 多态性 returnpoint(p1.x+p2.x,p1.y+p2.y); pointoperator-(pointp1,pointp2) returnpoint(p1.x-p2.x,p1.y-p2.y); voidmain() 第8章 多态性 pointp1(3,3),p2(2,2),p3,p4; /声明point类的对象 p3=p1+p2;/两点相加 p4=p1-p2;/两点相减 cout,=,=,!=) 。 赋值运算符重载(如=,+=,-=,*=,/=)。 下标运算符“”重载。 下标运算符“”通常用于取数组中的某个元素,通 过下标运算符重载,可以实现数组下标的越界检测等 。 第8章 多态性 运算符new和delete重载。 通过重载new和delete,可以克服new和delete的不足 ,使其按要求完成对内存的管理。 逗号运算符“,”重载。 逗号运算符是一个双目运算符,和其它运算符一样 ,我们也可以通过重载逗号运算符来达到期望的结果 。逗号运算符构成的表达式为“左操作数,右操作数” ,该表达式返回右操作数的值。 第8章 多态性 8.3 虚函数 8.3.1为什么要引入虚函数 为什么要引入虚函数,我们来看一个例子。 【例8-4】没有使用虚函数的例题。 #include classbase/定义基类base public: 第8章 多态性 voidwho() coutwho(); 第8章 多态性 ptr= ptr-who(); ptr= ptr-who(); obj1.who(); obj2.who(); return1; 此例在main()函数中定义了一个基类对象obj,和 两个派生类对象obj1与obj2,又定义了一个指向基类对 象的指针ptr。 第8章 多态性 此程序的意图是用ptr指针分别指向不同的对象, 以便执行不同对象所对应的类的成员函数。当ptr指向 obj对象时,ptr-who()调用base类的成员函数who(); 当ptr指向obj1对象时,我们希望ptr-who()调用derive1 类的成员函数who();而当ptr指向obj2对象时,则希望 ptr-who()调用derive2类的成员函数who()。此程序执 行后实际得到的结果为 第8章 多态性 thisistheclassofbase!(a) thisistheclassofbase!(b) thisistheclassofbase!(c) thisistheclassofderive1!(d) thisistheclassofderive2!(e) 第8章 多态性 在运行结果中,(a)、(d)和(e)与所预想的相符,而 (b)和(c)却不是希望得到的。这说明,不管指针ptr当前 指向哪个对象(是基类对象还是派生类对象),ptr- who()调用的都是基类中定义的who()函数。也就是说 ,通过指针引起的普通成员函数调用,仅仅与指针的 类型有关,而与指针正指向什么对象无关。在这种情 况下,必须采用显式的方式调用派生类的函数成员。 第8章 多态性 例如: obj1.who()或obj2.who() 或者是采用对指针的强制类型转换的方法,例如: (derive1*)ptr)-who()或(derive2*)ptr)-who() 本来使用对象指针是为了表达一种动态的性质, 即当指针指向不同对象时执行不同的操作,现在看来 并没有起到这种作用。要实现这种功能,就需要引入 虚函数的概念。这里,只需将基类的who()函数声明为 虚函数即可。 第8章 多态性 8.3.2 虚函数的定义及使用 1.虚函数的定义 虚函数的定义是在基类中进行的。它是在基类中 需要定义为虚函数的成员函数的声明中冠以关键字 virtual。当基类中的某个成员函数被声明为虚函数后, 此虚函数就可以在一个或多个派生类中被重新定义, 在派生类中重新定义时,其函数原型,包括返回类型 、函数名、参数个数、参数类型以及参数的顺序都必 须与基类中的原型完全相同。 第8章 多态性 一般虚函数的定义语法如下: virtual(形参表) 函数体 其中,被关键字virtual说明的函数为虚函数。特别 要注意的是,虚函数的声明只能出现在类声明中的函数 原型声明中,而不能出现在成员的函数体实现的时候。 第8章 多态性 需要注意,动态联编只能通过成员函数来调用或 者通过指针、引用来访问虚函数。如果使用对象名的 形式访问虚函数,则将采用静态联编方式调用虚函数 ,而无需在运行过程中进行调用。下面是通过指针访 问虚函数的例题。 第8章 多态性 【例8-5】使用虚函数例题。 #include classbase/定义基类base public: virtualvoidwho()/虚函数声明 coutwho(); 第8章 多态性 ptr= ptr-who(); ptr= ptr-who(); return1; 此时,程序运行结果为 thisistheclassofbase! thisistheclassofderive1! thisistheclassofderive2! 第8章 多态性 分析一下上面这个程序,在基类中对voidwho()进 行了虚函数声明,这样,在其派生类中就可以重新定 义它。在派生类derive1和derive2中分别重新定义 voidwho()函数,注意,此虚函数在派生类中重新定义 时不再需要virtual声明,此声明只在其基类中出现一次 。在voidwho()函数被重新定义时,其函数的原型与基 类中的函数原型必须完全相同。 第8章 多态性 在main()函数中,定义了一个指向基类类型的指针,它 也被允许指向其派生类。在执行过程中,不断改变它所 指向的对象,ptr-who()就能调用不同的版本。虽然都 是ptr-who()语句,但是,当ptr指向不同的对象时,所 对应的执行动作就不同。由此可见,用虚函数充分体现 了多态性。并且,因为ptr指针指向哪个对象是在执行过 程中确定的,所以体现的又是一种动态的多态性。 第8章 多态性 2. 虚函数与重载的关系 在一个派生类中重新定义基类的虚函数是函数重载 的另一种特殊形式,但它不同于一般的函数重载。 一般的函数重载,只要函数名相同即可,函数的返 回类型及所带的参数可以不同。但当重载一个虚函数时 ,也就是说在派生类中重新定义此虚函数时,要求函数 名、返回类型、参数个数、参数类型以及参数的顺序都 与基类中的原型完全相同,不能有任何的不同。 第8章 多态性 3多继承中的虚函数 在多继承中由于派生类是由多个基类派生而来的 ,因此,虚函数的使用就不像单继承那样简单。请看 下面的例题。 【例8-6】多继承中使用虚函数例题。 #include classbase1/定义基类base1 public: virtualvoidwho()/函数who()为虚函数 第8章 多态性 coutwho(); ptr2= ptr2-who(); ptr1= ptr1-who(); ptr2= ptr2-who(); return1; 第8章 多态性 此时,程序执行的结果为 thisistheclassofbase1! thisistheclassofbase2! thisistheclassofderive! thisistheclassofbase2! 从上面的例子看出,派生类derive中的函数who()在 不同的场合呈现不同的性质。如相对base1路径,由于在 base1中的who()函数前有关键字virtual,所以它是一个虚 函数;若相对于base2派生路径,在base2中的who()函数 为一般函数,所以,此时它只是一个重载函数。 第8章 多态性 当base1类指针指向derive类对象obj3时,函数 who()就呈现出虚特性;当base2类指针指向derive类对 象obj3时,函数只呈现一般的重载特性。 若一个派生类,它的多个基类中有公共的基类, 在公共基类中定义一个虚函数,则多重派生以后仍可 以重新定义虚函数,也就是说,虚特性是可以传递的 。请看下面的例题。 第8章 多态性 【例8-7】多继承中虚特性的传递例题。 #include classbase /定义基类base public: virtualvoidwho()/定义虚函数 coutwho(); ptr2= ptr2-who(); return1; 此时,程序执行的结果为 thisistheclassofderive! thisistheclassofderive! 第8章 多态性 从本例题可以看出,虚特性是可以传递的。base类作为 base1和base2类的直接基类,它的成员函数who()被声 明为虚函数,则base1和base2类中的who()都具有虚特性 ,即均为虚函数;而derive类为base1和base2类的派生 类,因此,它的成员函数who()也为虚函数。 第8章 多态性 8.3.3 虚函数的限制 如果我们将所有的成员函数都设置为虚函数,当 然是很有益的。它除了会增加一些额外的资源开销, 没有什么坏处。但设置虚函数须注意以下几点。 只有成员函数才能声明为虚函数。因为虚函数 仅适用于有继承关系的类对象,所以普通函数不能声 明为虚函数。 第8章 多态性 虚函数必须是非静态成员函数。这是因为静态成 员函数不受限于某个对象。 内联函数不能声明为虚函数。因为内联函数不 能在运行中动态确定其位置。 构造函数不能声明为虚函数。多态是指不同的 对象对同一消息有不同的行为特性。虚函数作为运行 过程中多态的基础,主要是针对对象的,而构造函数 是在对象产生之前运行的,因此,虚构造函数是没有 意义的。 第8章 多态性 析构函数可以声明为虚函数。析构函数的功能 是在该类对象消亡之前进行一些必要的清理工作。析 构函数没有类型,也没有参数,和普通成员函数相比 ,虚析构函数情况略为简单些。 第8章 多态性 虚析构函数的声明语法如下: virtual类名 例如: classB public: / virtualB(); ; 第8章 多态性 8.4 抽象类 8.4.1 纯虚函数 一个抽象类至少带有一个纯虚函数。纯虚函数是一 个在基类中说明的虚函数,它在该基类中没有定义具体 的操作内容,要求各派生类根据实际需要定义自己的实 现内容。纯虚函数的声明形式如下: virtual(参数表)=0 纯虚函数与一般虚函数在书写形式上的不同在于 其后面加了“=0”,表明在基类中不用定义该函数,它的 实现部分函数体留给派生类去做。 第8章 多态性 8.4.2 抽象类 抽象类的主要作用是通过它为一个类族建立一个公 共的接口,使它们能够更有效地发挥多态特性。使用 抽象类时需注意以下几点。 抽象类只能用作其它类的基类,不能建立抽象 类对象。抽象类处于继承层次结构的较上层,一个抽 象类自身无法实例化,而只能通过继承机制,生成抽 象类的非抽象派生类,然后再实例化。 第8章 多态性 抽象类不能用作参数类型、函数返回值或显式 转换的类型。 可以声明一个抽象类的指针和引用。通过指针 或引用,我们就可以指向并访问派生类对象,

温馨提示

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

评论

0/150

提交评论