C程序设计-对象分册(第3章)潘修改.ppt

上传人:max****ui 文档编号:6331393 上传时间:2020-02-23 格式:PPT 页数:56 大小:431.81KB
返回 下载 相关 举报
C程序设计-对象分册(第3章)潘修改.ppt_第1页
第1页 / 共56页
C程序设计-对象分册(第3章)潘修改.ppt_第2页
第2页 / 共56页
C程序设计-对象分册(第3章)潘修改.ppt_第3页
第3页 / 共56页
点击查看更多>>
资源描述
1 第3章多态性 本章学习重点掌握内容 多态的概念和作用 多态的实现方法常见运算符的重载静态联编和动态联编虚函数 纯虚函数和抽象基类的概念和用法虚析构函数的概念和作用 虚析构函数的用法 2 第3章多态性 3 1多态性的概念3 2运算符重载3 3联编和虚函数3 4纯虚函数和抽象类3 5综合应用实例 3 3 1多态性的概念 多态性 Polymorphism 是面向对象程序设计的重要特性之一 它与封装性和继承性一起构成了面向对象程序设计的三大特性 多态性是指当不同的对象收到相同的消息时 产生不同的动作 利用多态性可以设计和实现一个易于扩展的系统 在面向对象程序设计里多态性主要体现在 向不同的对象发送同一个消息 不同的对象在接收时会产生不同的行为 即方法 也就是说 每个对象可以用自己的方式去响应共同的消息 C 支持两种形式的多态性 一种是编译时的多态性 称为静态联编 4 3 2 1运算符重载概述 在以前的学习中 C 中预定义的运算符的操作对象只能是基本数据类型如int或float等 实际上 对于很多用户自定义的类型 如类 也需要有类似的运算操作 例如复数类Complex classComplex public Complex real image 0 Complex doubler doublei real r image i voidPrint private doublereal image 5 3 2 1运算符重载概述 voidComplex Print if image 0 cout real image i elsecout real image i 声明复数类的对象 complexc1 2 0 3 0 c2 4 0 2 0 c3 如果我们需要对c1和c2进行加法运算 c3 c1 c2 编译时却会出错 这是因为编译器不知道该如何完成这个加法 这时我们就需要编写程序来实现 运算符来作用于complex类的对象 这就是运算符的重载 运算符重载是对已有的运算符赋予多重含义 使同一个运算符作用于不同类型的数据时 导致不同类型的行为 6 3 2 1运算符重载概述 C 中运算符的重载虽然给我们设计程序带来很多的方便 但对运算符的重载时 以下的几种情况需要注意 1 一般来说 不改变运算符原有含义 只让它能针对新类型数据的实际需要 对原有运算符进行适当的改造 例如 重载 运算符后 它的功能还是进行加法运算 2 重载运算符时 不能改变运算符原有的优先级别 也不能改变运算符需要的操作数的数目 重载之后运算符的优先级和结合性都不会改变 3 不能创建新的运算符 只能重载c 中已有的运算符 4 有些运算符不能进行重载 如 类成员运算符 类指向运算符 类作用域运算符 条件运算符及 sizeof 求字节数运算符 5 重载运算符只适用于用户自定义的类的对象之间 以及它们和基本数据类型之间 的运算 C 基本数据之间的运算符的含义不能通过重载改变 7 3 2 2双目运算符重载 双目运算符就是运算符作用于两个操作数 下面通过一个例子对 运算符重载来学习一下双目运算符重载的应用 例3 1 定义一个复数类 重载 运算符为复数类的成员函数 使这个运算符能直接完成两个复数的加法运算 以及一个复数与一个实数的加法运算 C C 程序设计教程 面向对象分册 friendComplexoperator constComplex includeusingnamespacestd classComplex public Complex doubler 0 doublei 0 real r image i voidprint if image 0 cout real image i elseif image 0 cout real image i elsecout real 例3 2 定义复数类Complex 重载 运算符为它的成员函数 includeclassComplex public Complex doubler 0 doublei 0 real r image i voidprint Complexoperator constComplex C C 程序设计教程 面向对象分册 C C 程序设计教程 面向对象分册 voidComplex print if image 0 cout real image i elsecout real image i cout endl ComplexComplex operator constComplex 运行结果 c3 c1 c2 125 250i该运算符重载函数仅有一个参数 可见 当运算符重载为类的成员函数时 双目运算符仅有一个参数 这个原因是成员函数总是隐含着一个参数this指针 C C 程序设计教程 面向对象分册 例3 3 定义一个日期类date 中采用友元形式重载 运算符 实现日期加上一个天数 得到新日期 includeusingnamespacestd staticintmon day 31 28 31 30 31 30 31 31 30 31 30 31 classCDate public CDate intm 0 intd 0 inty 0 month m day d year y voiddisplay cout month day year endl friendCDateoperator intd CDatedt 友元形式重载 运算符private intmonth day year C C 程序设计教程 面向对象分册 CDateoperator intd CDatedt 重载 运算符 dt day dt day d while dt day mon day dt month 1 dt day mon day dt month 1 if dt month 13 dt month 1 dt year returndt intmain CDateolddate 2 20 99 CDatenewdate newdate 21 olddate newdate display return0 运行结果 3 13 99 13 3 2 3赋值运算符重载 在C 中有两种类型的赋值运算符 一类是是 即直接赋值的运算符 另一类是 和 等复合赋值运算符 下面以 运算符为例子讨论 复合赋值运算符的重载作为练习 读者可以自行推导 下面分别进行讨论 例3 5 运算符重载的示例 include includeusingnamespacestd classCMessage public CMessage buffer newchar 0 CMessage delete buffer voiddisplay cout buffer endl voidset char string delete buffer buffer newchar strlen string 1 strcpy buffer string CMessage 15 voidmain CMessagec1 c1 set initialc1message c1 display CMessagec2 c2 set initialc2message c2 display c1 c2 c1 display 运行结果 initialc1messageinitialc2messageinitialc2message 16 3 2 4单目运算符重载 类的单目运算符可重载为一个没有参数的非静态成员函数或者带有一个参数的非成员函数 参数必须是用户自定义类型的对象或者是对该对象的引用 在C 中 单目运算符有 和 它们是变量自动增1和自动减1的运算符 在类中可以对这两个单目运算符进行重载 如同 运算符有前缀 后缀两种使用形式 和 重载运算符也有前缀和后缀两种运算符重载形式 以 重载运算符为例 其语法格式如下 函数类型operator 前缀运算函数类型operator int 后缀运算使用前缀运算符的语法格式如下 对象 使用后缀运算符的语法格式如下 对象 17 例3 5 重载单目运算符 includeusingnamespacestd classCounter public Counter inti 0 v i Counteroperator 前置单目运算符Counteroperator int 后置单目运算符voidgetValue cout v endl private intv CounterCounter operator 前置单目运算符 v return this CounterCounter operator int 后置单目运算符 Countert t v v returnt 18 intmain Counterc1 5 c2 5 c3 c4 inti c3 c1 后置单目运算符cout c3 v c3 getValue c4 c2 前置单目运算符cout c4 v c4 getValue return0 运行结果 c3 v 5c4 v 6 19 3 2 5下标运算符重载 下标运算符 通常用于在数组中标识数组元素的位置 下标运算符重载可以实现数组数据的赋值和取值 下标运算符重载函数只能作为类的成员函数 不能作为类的友元函数 下标运算符 函数重载的一般形式为 函数类型operator 形参表 其中形参表为该重载函数的参数列表 重载下标运算符只能且必须带一个参数 该参数给出下标的值 例3 6 定义一个字符数组类 其中对下标运算符 进行重载 include includeusingnamespacestd classMyCharArray public MyCharArray intm 20 len m str newchar len memset str 0 len MyCharArray char s str newchar strlen s 1 strcpy str s len strlen s MyCharArray delete str char private intlen char str voidmain MyCharArrayword ThisisaC program word Disp cout 位置0 word 0 endl cout 位置15 word 15 endl cout 位置25 word 25 endl word 0 t word Disp intf 11 MyCharArrayword2 f for inti 0 i 10 i word2 i word i word2 Disp 21 运行结果 ThisisaC program 位置0 T位置15 r位置25 整数下标越界thisisaC program thisisa说明 1 在重载下标 运算符函数中 其返回值char型引用 因为该类的一个对象word 代表一个数组 而word i 是代表编号为i的数组元素 它可能出现在赋值语句的左端 所以重载下标 运算符函数的返回值类型必须是引用类型的 2 在重载下标 运算符函数中 首先检查函数参数n的范围当它超出范围发出一个信息 并返回一个static整数的引用 可以避免修改内存区的内容 3 该字符数组类优点是定义数组大小时不必是一个常量 22 3 2 6类型转换运算符重载 C 的基本数据类型之间可以互相转换 转换的方式有隐含转换和强制转换两种 doublea 2 3 intm 1 doubleb b a m 隐含转换 将m自动转变成double型b a double m 强制类型转换 将m强制转变成double型对于用户自定义的类的对象 也可以实现它和基本类型数据之间的转换 这种转换有两种 第一 从基本类型数据转变成对象 可以借助构造函数来实现 第二 从对象转变成基本类型数据 使用类型转换重载运算符函数 23 1 从基本数据类型转变成对象 如果一个类有带一个参数 基本数据类型 的构造函数 它就可以起到把基本类型数据转变成对象的作用 在遇到对象和基本类型数据的混合运算时 编译器会自动将基本类型数据转变成对象 24 例3 7 定义人民币Money类 利用构造函数实现从基本数据double到Money对象的自动转换 includeusingnamespacestd ClassMoney public Money doubled value d voidshow cout value friendMoneyoperator Moneym1 Moneym2 private doublevalue Moneyoperator Moneym1 Moneym2 returnMoney m1 value m2 value 25 intmain MoneymyMoney 82 5 Moneymo myMoney 16 6 编译器利用构造函数自动把16 6转变成对象mo show 运行结果 99 1说明 当编译器处理表达式 myMoney 16 6 时 它会自动寻找能将两者变成同一种数据类型的转换方法 Money类的构造函数就可以起到这样的作用 把16 6作为实参传递给它 就可以产生一个对象 然后 编译器调用重载的 运算符函数把该对象和myMoney相加 26 2 从对象转变成基本类型数据 可以通过定义类型转换运算符重载函数的格式来实现把对象自动转换成基本类型数据和把对象强制转换成基本类型数据 operator类型名 函数体 与以前的重载运算符函数不同的是 类型转换运算符重载函数没有返回类型说明 因为类型名就代表了它的返回类型 而且也没有任何参数 在调用过程中要带一个对象实参 实际上 类型转换运算符将对象转换成类型名规定的类型 转换时的形式就像强制转换一样 如果没有转换运算符定义 直接用强制转换是不行的 因为强制转换只能对标准数据类型进行操作 对类类型的操作是没有定义的 27 例3 8 实现人民币Money与double的转换 在主函数中将double数分别显式和隐式转换成Money对象 includeusingnamespacestd classMoney 人民币类 public Money doubled Value d voidshow cout value endl operatordouble 类型转换运算符重载函数 returnvalue private doublevalue 28 intmain Moneyr1 3 6 r2 2 4 doublesum double r1 double r2 显式转换类型cout sum endl doublesurplus 8 0 r1 隐式转换类型cout surplus return0 运行结果 6 04 4说明 1 double r1 double r2 是显式转换类型 C 系统处理成r1 operatordouble 实现将r1对象转换成实数值 2 表达式 8 0 r1 是隐含类型转换 由于 运算符两侧的类型不一致 编译器会自动寻找能将两者转换成相同类型的方法 类型转换运算符重载函数可以起到这样的作用 编译器利用类型转换运算符函数把r1自动转变成double型数据3 6 然后计算8 0 3 6 29 3 3联编和虚函数 3 3 1静态联编和动态联编面向对象的多态性从实现的角度来讲 可以分为静态多态性和动态多态性两种 在源程序编译的时候就能确定具有多态性的语句调用哪个函数 称为静态联编 对于重载函数的调用就是在编译的时候确定具体调用哪个函数 所以是属于静态联编 从对静态联编的上述分析中可以知道 编译程序在编译阶段并不能确切知道将要调用的函数 只有在程序执行时才能确定将要调用的函数 为此要确切知道该调用的函数 要求联编工作要在程序运行时进行 这种在程序运行时进行联编工作被称为动态联编 或称动态束定 又叫晚期联编 30 3 5 23 3 2虚函数的引入 在C 中 动态联编是通过虚函数来实现的 虚函数的本质是将派生类类型的指针赋给基类类型的指针 虚函数被调用时会自动判断调用对象的类型 从而做出相应的响应 在介绍虚函数之前 我们首先来看一个例子 31 includeusingnamespacestd constdoublePI 3 14159 classCPoint public CPoint doublex doubley this x x this y y doublearea return0 private doublex y classCCircle publicCPoint public CCircle doublex doubley doubleradius CPoint x y this radius radius doublearea returnPI radius radius private doubleradius 例3 10 虚函数的引入 32 intmain CPointpoint 3 0 4 0 CCirclecircle 5 0 6 0 10 coutarea area area endl return0 程序的执行结果为 areaofCPointis0areaofCCircleis314 159areaofptr circleis0areaofptr circleis314 159areaofCCircleis0 33 本程序中 CPoint为基类 CCircle为它的派生类 而且 CCircle覆盖 改写 了它继承的基类CPoint的成员函数area 此时基类的area函数和派生类的area函数做的事情是不一样的 在main函数中 分别创建了基类的对象point和派生类的对象circle 然后 分别声明基类指针ptr point和派生类指针ptr circle 随后 基类和派生类对象分别调用自己的area函数 指向基类对象的基类指针和指向派生类对象的派生类指针分别间接调用基类和派生类的area函数 这些结果都是平凡的 但是 如果我们令基类指针ptr point指向派生类对象circle 当ptr point指针调用area函数时 被调用的是基类的area函数 还是派生类的area函数 答案是 ptr point调用的是基类的area函数 这是因为对于类的普通成员函数 C 编译器采取的是静态联编技术 在编译的时候就确定ptr point将要调用的area函数的代码 而ptr point是一个基类的指针 与它绑定的是基类的area函数的代码 这个结果从逻辑上看是不合理的 众所周知 面向对象的核心思想之一就是把数据 成员变量 和对数据的操作 成员函数 封装起来形成一个整体 在 3 10 中 ptr point指向的对象是一个派生类对象 如果通过ptr point指针来间接访问成员变量 则访问到的成员变量属于派生类 然而 通过ptr point指针来间接调用成员函数 被调用的成员函数却是基类的成员函数 合理的方案应该是 因为ptr point指向的是一个派生类对象 当通过ptr point指针间接调用area函数时 其逻辑意义本应是这个派生类对象来调用area函数 那么被调用的就应当是派生类的area函数 这个目标是可以实现的 前提是被调用的函数不是普通成员函数 而是虚函数 34 3 3 3虚函数的定义和多态性 虚函数是一种特殊的成员函数 定义虚函数的语法如下 virtual函数类型函数名 形参表 函数体 普通的外部函数不能声明为虚函数 虚函数只能是类中的一个成员函数 虚函数只能是实例成员函数 不能是静态成员函数 虚函数具有强继承性 一旦基类定义了虚函数 该基类的所有派生类以及间接派生类中的继承的该函数也自动成为虚函数 换句话说 当在派生类中定义了一个同名的成员函数时 只要该成员函数的参数个数和相应类型以及它的返回类型与基类中同名的虚函数完全一样 如上例中的voidarea void 函数 则无论是否为该成员函数使用virtual 它都将成为一个虚函数 35 关于虚函数有以下几点说明 1 当基类中把成员函数定义为虚函数后 要达到动态联编的效果 派生类必须覆盖基类的虚函数 即派生类中定义的函数和基类的对应成员函数不仅名字相同 而且返回类型 参数个数和类型也必须相同 否则不能实现运行时的多态 2 基类中虚函数前的virtual不能省略 派生类中的虚函数的virtual关键字可以省略 缺省后仍为虚函数 3 运行时多态必须通过基类对象的引用或基类对象的指针调用虚函数才能实现 4 虚函数必须是类的成员函数 但是不能是静态成员函数 5 一个非成员的外部函数是不能定义成虚函数的 即使它是某个类的友元函数 5 不能将构造函数定义为虚函数 但可将析构函数定义为虚函数 36 例3 11 对于上面的例子 把基类的成员函数定义为虚函数 分析运行结果 includeusingnamespacestd constdoublePI 3 14159 classCPoint public CPoint doublex doubley this x x this y y virtualdoublearea return0 private doublex y 37 classCCircle publicCPoint public CCircle doublex doubley doubleradius CPoint x y this radius radius doublearea returnPI radius radius private doubleradius intmain CPointpoint 3 0 4 0 CCirclecircle 5 0 6 0 10 coutarea area area endl return0 38 程序的运行结果为 areaofCPointis0areaofCCircleis314 159areaofptr circleis0areaofptr circleis314 159areaofCCircleis314 159说明 从上面的结果我们确实看到 指向派生类对象circle的基类指针ptr point调用虚成员函数area时 被调用的是派生类的成员函数 39 例3 12 多态性的表现 includeusingnamespacestd constdoublePI 3 14159 classCPoint public CPoint doublex doubley this x x this y y virtualdoublearea return0 private doublex y classCCircle publicCPoint public CCircle doublex doubley doubleradius CPoint x y this radius radius doublearea returnPI radius radius private doubleradius classCRectangle publicCPoint public CRectangle doublex doubley doublewidth doubleheight CPoint x y this width width this height height doublearea returnwidth height private doublewidth height 40 intmain CPointpoint 3 0 4 0 CCirclecircle 5 0 6 0 10 CRectanglerect 1 2 3 2 5 0 6 0 CPoint ptr point 程序的运行结果为 areaofptr pointis0areaofptr pointis314 159areaofptr pointis30说明 本程序中 只有一个CPoint类的指针ptr point 当它分别指向基类CPoint对象point 派生类CCircle的对象circle 派生类CRectangle的对象rect时 同样执行ptr point area 调用的是不同的area函数 同一个表达式 ptr point area 在不同的场合可以执行不同的行为 这就是 多态性 的含义 41 3 3 4使用引用变量的多态性 除了可以用一个基类指针指向派生类对象之外 我们还可以使用一个基类引用 将它初始化到派生类的对象 那么 通过引用变量来调用虚函数 同样可以体现多态性 例3 13 引用变量的多态性 includeusingnamespacestd constdoublePI 3 14159 classCPoint public CPoint doublex doubley this x x this y y virtualdoublearea return0 private doublex y classCCircle publicCPoint public CCircle doublex doubley doubleradius CPoint x y this radius radius doublearea returnPI radius radius private doubleradius 42 intmain CPointpoint 3 0 4 0 CCirclecircle 5 0 6 0 10 cout areaofCPointis point area endl cout areaofCCircleis circle area endl CPoint 程序的运行结果为 areaofCPointis0areaofCCircleis314 159areaofptr circleis0areaofptr circleis314 159areaofCCircleis314 159说明 由上面的结果可以得出 指向派生类对象circle的基类引用re point1调用虚成员函数area时 被调用的是派生类的成员函数 注意 和指针变量不同 引用变量一旦初始化到一个对象之后 就不可能再改变指向 在 例3 13 中的一个基类指针可以先后指向基类和两个派生类的对象 从而在不同的场合执行不同的行为 而对于引用变量 这是不可能 但是使用引用变量调用虚函数时 起作用的仍然是动态联编的机制 同样可以体现多态性 最后 综合3 3 4和3 3 4节的内容 C 里的运行时的多态性 动态联编和虚函数机制 可以概括成 指向派生类对象的基类指针 引用 调用虚成员函数时 被调用的是派生类的成员函数 43 3 3 5动态联编的要素 指针 引用 变量 根据第二章里介绍的赋值兼容规则 我们不但可以用一个基类指针 引用 变量指向派生类对象 而且还可以把一个派生类的对象直接赋给一个基类对象 根据多态性的定义 指向派生类对象的基类指针 引用 调用虚函数的时候 编译器执行的是动态联编 被调用的是派生类的成员函数 那么 一个被赋值的基类对象 当它调用虚函数时 是不是动态联编呢 能不能体现多态性呢 44 例3 14 引用变量的多态性 includeusingnamespacestd constdoublePI 3 14159 classCPoint public CPoint doublex doubley this x x this y y virtualdoublearea return0 private doublex y classCCircle publicCPoint public CCircle doublex doubley doubleradius CPoint x y this radius radius doublearea returnPI radius radius private doubleradius intmain CPointpoint 3 0 4 0 CCirclecircle 5 0 6 0 10 cout areaofCPointis point area endl cout areaofCCircleis circle area endl point circle cout areaofCCircleis point area endl return0 程序的运行结果为 areaofCPointis0areaofCCircleis314 159areaofCPointis0说明 由上面的结果可以得出 把派生类对象circle赋给基类对象point 被赋值的基类对象point调用虚成员函数area时 被调用的是基类的成员函数 显然 这是静态联编 不能表现多态性 换句话说 动态联编的实现必须依赖于指针或者引用变量 这是多态性必不可少的基本要素 45 3 3 6动态联编的工作机制 那么动态联编到底是如何工作的呢 它和静态联编的区别是什么呢 接下来我们结合上面的例子对动态联编的机制作一个分析 第一 动态联编需要两个前提 1 通过指针和引用变量来调用 2 被调用的是虚函数 而 例3 14 中 通过基类对象point来调用area函数 不满足上述前提 1 因此属于静态联编 也就是说 在编译的时候 编译器已经确定被调用的area函数的代码 由于point是一个基类对象 所以 与它绑定的只能是基类的area函数的代码 同样地 例3 10 中 被调用的函数不是虚函数 不满足前提 2 仍然是静态联编 所以 当程序编译时 遇到表达式prt point area 时 由于ptr point是一个基类指针 编译器自动绑定基类的area函数代码 这就是两个例子中被调用的area函数是基类的成员函数的原因 46 第二 对于 例3 11 例3 12 例3 13 它们符合动态联编的前提 则编译器在执行过程中遇到virtual关键字的时候 将自动执行动态联编的机制 1 首先为这些包含virtual函数的类 注意不是对象 建立一张虚函数表VTABLE 在这些虚函数表中 编译器将依次按照函数声明次序记录类的所有虚函数的代码在内存中存储区域的首地址 2 同时在每个带有虚函数的类中增加一个称之为vpointer的指针 简称vptr 这个指针指向这个类的VTABLE 3 派生类继承了基类的虚函数表VTABLE 并且加以修正 如果派生类覆盖了继承到的虚函数 则VTABLE中登记的就是派生类改写后的虚函数代码的首地址而非基类的虚函数代码的首地址 如果派生类新增了虚函数 则在VTABLE中增加一项 记录新增虚函数的代码的首地址 3 在编译的时候 仅仅针对非虚的成员函数进行代码绑定 而暂时不会绑定虚函数 4 在程序运行时 每一个对象创建时 都复制一份vptr指针保存在自己的内存空间 一般置于对象的起始位置 5 当指向派生类对象的基类指针 引用 来调用某个虚函数时 才来做代码绑定工作 先根据指针 引用 找到它所指向的派生类对象 然后 从对象中获取vptr指针 从而找到VTABLE 从VTABLE查找到该虚函数的代码在内存空间中存储区域的首地址 进一步找到它的代码 最后实现绑定和调用 注意 上述调用过程中是通过派生类的对象来找到最终被调用的虚函数代码的 所以 被调用的只能是派生类的成员函数 这就是C 中动态联编 多态性 的实现机制 47 3 3 7虚析构函数 虚析构函数的声明语法为 virtual 类名 例3 15 虚析构函数的用法和作用示例 include includeusingnamespacestd classCEmployee public CEmployee char Name intAge virtual CEmployee private char name intage CEmployee CEmployee char Name intAge name newchar strlen Name 1 strcpy name Name age Age CEmployee CEmployee cout DestructCEmployee endl if name delete name classCTeacher publicCEmployee public CTeacher char Name char Course intAge CTeacher private char course CTeacher CTeacher char Name char Course intAge CEmployee Name Age course newchar strlen Course 1 strcpy course Course CTeacher CTeacher cout DestructCTeacher endl if course delete course 48 intmain CEmployee p 3 p 0 newCEmployee lihua 20 基类指针指向基类对象p 1 newCTeacher liuming C 26 基类指针指向派生类对象p 2 newCTeacher liming Datastructure 30 基类指针指向派生类对象for inti 0 i 3 i deletep i 删除指向父类的指针CEmployeereturn0 运行结果 DestructCEmployeeDestructCTeacherDestructCEmployeeDestructCTeacherDestructCEmployee 如果父类CEmployee的析构函数没有声明为virtual 则程序的输出结果为 DestructCEmployeeDestructCEmployeeDestructCEmployee请读者自行分析程序的输出结果 49 3 4纯虚函数和抽象类 3 4 1纯虚函数在许多情况下 在基类中无法为虚函数给出一个有意义的定义 这时可以将它说明为纯虚函数 例如在本章的例3 11程序中点没有面积 可以说明为 virtualdoublearea 0 这就将函数doublearea 声明为一个纯虚函数 purevirtualfunction 把它的具体定义留给派生类来做 纯虚函数是在声明虚函数时被 初始化 为0的函数 纯虚函数只有函数的名字而不具备函数的功能 不能被调用 它只是通知编译系统 在这里声明一个虚函数 留待派生类中定义 在派生类中对此函数提供定义后 它才能具备函数的功能 可被调用 纯虚函数的作用是在基类中为其派生类保留一个函数的名字 以便派生类根据需要对它进行定义 如果在基类中没有保留函数名字 则无法实现多态性 50 在C 中 对于那些在基类中不需要定义具体的行为的函数 可以定义为纯虚函数 声明纯虚函数的一般形式是class类名 virtual类型函数名 参数表 0 纯虚函数 注意 1 纯虚函数没有函数体 2 最后面的 0 并不表示函数返回值为0 它只起形式上的作用 告诉编译系统 这是纯虚函数 3 这是一个成员函数声明语句 最后应有分号 4 如果在一个类中声明了纯虚函数 而在其派生类中没有覆盖该函数 则该虚函数在派生类中仍然为纯虚函数 5 纯虚函数是虚函数的一种 因此它具有虚函数的一切性质 同样可以体现多态性 51 3 4 2抽象类 如果一个类中至少有一个纯虚函数 那么这个类被称为抽象类 abstractclass 因此抽象类的定义是基于纯虚函数的 抽象类中不仅包括纯虚函数 也可包括虚函数 抽象类中的纯虚函数可能是在抽象类中定义的 也可能是从它的抽象基类中继承下来且重定义的 抽象类有一个重要特点 即抽象类必须用作派生其他类的基类 而不能用于直接创建对象实例 抽象类不能直接创建对象的原因是其中有一个或多个函数没有定义 但仍可使用指向抽象类的指针支持运行时多态性 52 抽象类定义的一般形式是 class类名 public virtual函数名 参数表 0 其他函数的声明 在使用抽象类的时候 有下面三种情况需要注意 1 抽象类只能用作其他类的基类 抽象类不能建立对象 2 抽象类不能用作函数参数类型 函数返回值类型或显式转换的类型 3 可以声明抽象类的指针和引用 53 例3 16 抽象类示例 includeusingnamespacestd classCPerson public virtualvoidPrintInfo cout 人类 n virtualvoidDisplaySalary intm doubles 0 classCWorker publicCPerson public voidPrintInfo cout 工人 n voidDisplaySalary intm doubles cout 工人全年的工资是 m s endl private intkindofwork classCTeacher publicCPerson public voidPrintInfo cout 教师 n voidDisplaySalary intm doubles cout 教师全年的工资是 m s endl private intsubject classCDriver publicCPerson public voidPrintInfo cout 司机 n voidDisplaySalary intm doubles cout 司机全年的工资是 m s endl private intsubject 54 intmain 分别声明Worker Teacher和Driver类的对象worker teacher driverCWorkerworker CTeacherteacher CDriverdriver CPerson person 指向父类的指针变量personperson 运行结果 工人工人全年的工资是 21604 2教师教师全年的工资是 15605 4司机司机全年的工资是 20409 4CPerson类中的虚函数DisplaySalary intm doubles 仅起到为派生类提供一个一致的接口的作用 派生类中重定义的DisplaySalary intm doubles 用于决定以什么样的方式计算工资 由于在CPerson类中不能对此做出决定 因此被说明为纯虚函数 由此可见 赋值兼容规则使人们可将工人 教师和司机等都视为人类的对象 多态性又保证了函数DisplaySalary intm doubles 在对不同的人群计算工资时 无须关心当前正在计算类人 在需要时 函数DisplaySalary intm doubles 可从这些不同人类的子类对象那里获得该对象的工资 55 3 5综合应用实例 实例一 设计一个字符串类 封装一个不定长字符串并实现字符串的基本操作 要求如下 1 定义一个串类Cstring 建立适当的构造函数 构造函数原形如下 Cstring Cstring Cstring Cstring constCstring stringscr Cstring Cstring constchar ps 2 对关系运算符重载 完成两个字符串的比较 函数原形如下 friendCstringoperator constCstring string1 constCstring string2 3 运用运算符重载 完成串的赋值与合并 函数原形如下 constCstring operator constCstring stringscr friendCstringoperator constCstring string1 constCstring string2 分析 在类中封装一个字符串 可采用数组或指针存储字符串 由于字符串不定长 采用指针存储较好 不会浪费内存空间 56 实例二 实现几何形体的派生 图3 2几何形体的派生关系对平面形体有周长和面积 对立体有表面积和体积 对几何图形基类 周长 面积和体积应怎样计算 用什么函数 对平面图形体积怎样计算 用什么函数 对立体图形周长怎么计算 用什么函数 要求实现运行时的多态性 请编程 并测试 分析 这是一个典型的多态问题 由于在基类Geometric shape中无法得知对周长 面积和体积应怎样计算 所以定义为纯虚函数 使得具体实现留到派生类 在派生类圆Circle 派生类矩形Rectangle 派生类三角形Triangle实现周长 面积计算 在派生类长方体Box 和圆柱体Cylinder实现体积计算 同时注意运行时的多态性要用基类指针实现
展开阅读全文
相关资源
相关搜索

当前位置:首页 > 图纸专区 > 课件教案


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!