




已阅读5页,还剩137页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第3章C 面向对象程序设计 版权所有复制必究 本章主要内容 类和对象类继承多态性 虚函数 重载 模板 与传统的面向过程的程序设计语言相比 C 语言的最大特征是支持面向对象程序设计OOP ObjectOrientedProgramming 它引入了类 继承 多态和重载等面向对象的新机制 通过本章的学习 使我们系统地介绍C 面向对象设计的基本方法 结构化程序设计的特点 是一种自上而下 逐步细化的模块化程序设计方法 WirthN的观点 算法 数据结构 程序是一种面向过程程序设计方法 即一个程序是由多个过程 在C 中为函数 模块组成 过程之间通过函数参数和全局变量进行相互联系 3 1面向对象程序设计概述 3 1 1结构化程序设计 与非结构化程序相比 结构化程序在调试 可读性和可维护性等方面都有很大的改进 代码重用性不高 以过程为中心设计新系统 除了一些标准函数 大部分代码都必须重新编写 由于软 硬件技术的不断发展和用户需求的变化 按照功能划分设计的系统模块容易发生变化 使得开发出来的模块的可维护性欠佳 面向过程模式将数据与过程分离 若对某一数据结构做了修改 所有处理数据的过程都必须重新修订 这样就增加了很多的编程工作量 结构化程序设计的特点 什么是对象 现实世界是由各种各样的事物组成 包括真实的事物和抽象的事物 例如 人 动物 汽车 真实的事物 和程序 直线 抽象的事物 等 每一类事物都有自己特定的属性 如大小 形状 重量等 和行为 如生长 行走 转弯 运算等 人们通过研究事物的属性和行为而认识事物 在计算机科学中将这些现实世界中的事物称之为对象 对象是包含现实世界中事物特征的抽象实体 它反映了系统为之保存信息和与之交互的方法 在程序设计领域 可以用如下公式表示 对象 数据 作用于这些数据上的操作 3 1 2面向对象程序设计方法及特征 为了描述属性和行为相同的一类对象 引入了类 class 的概念 类是具有相同数据结构 属性 和相同操作功能 行为 的对象的集合 它规定了这些对象的公共属性和行为方法 对象是类的一个实例 例如 汽车是一个类 而行驶在公路上的一辆汽车则是一个对象 对象和类的关系相当于程序设计语言中变量和变量类型的关系 什么是类 OOP围绕现实世界的概念来组织模块 采用对象描述问题空间的实体 用程序代码模拟现实世界中的对象 使程序设计过程更自然 更直观 SP是以功能为中心来描述系统 而OOP是以数据为中心来描述系统 相对于功能而言 数据具有更强的稳定性 OOP模拟了对象之间的通信 就象人们之间互通信息一样 对象之间也可以通过消息进行通信 这样 我们不必知道一个对象是怎样实现其行为的 只需通过对象提供的接口进行通信并使用对象所具有的行为功能 面向对象程序设计的特点 OOP把一个复杂的问题分解成多个能够完成独立功能的对象 类 然后把这些对象组合起来去完成这个复杂的问题 一个对象可由多个更小的对象组成 如汽车由发动机 传送系统和排气系统等组成 这些对象 类 可由不同的程序员来设计 可在不同程序中使用 就象一个汽车制造商使用许多零部件去组装一辆汽车 而这些零部件可能不是自己生产的 采用面向对象模式就象在流水线上工作 我们最终只需将多个零部件 已设计好的对象 按照一定关系组合成一个完整的系统 面向对象程序设计的特点 classTime private inthour 数据成员 表示小时intminute 数据成员 表示分钟intsecond 数据成员 表示秒public voidsetTime inth intm ints 成员函数 设置时间 hour h 0 一个简单例子 main TimeEndTime 声明对象EndTime 设置对象EndTime的时间 属性 数据成员 EndTime setTime 12 23 36 cout Thetimeis 显示对象EndTime的时间EndTime showTime 运行结果 Thetimeis 12 23 36 面向对象程序设计方法的基本特征 面向对象程序设计方法具有四个基本特征 抽象封装继承多态性 1 抽象抽象是人类认识问题的最基本手段之一 抽象是指对具体问题 对象 进行概括 抽出一类对象的公共属性和行为并加以描述的过程 抽象包括数据抽象和代码抽象 或行为抽象 2 封装封装是把每个对象的数据 属性 和操作 行为 包装在一个类中 一旦定义了对象的属性和行为 则必须决定哪些属性和行为只用于表示内部状态 哪些属性和行为在外部是可见的 一般限制直接访问对象的属性 而应通过操作接口访问 这样使程序中模块之间关系更简单 数据更安全 对程序的修改也仅限于类的内部 使得由于修改程序所带来的影响局部化 3 继承继承是指一个新类可以从现有的类派生而来 新类继承了现有类的特性 包括一些属性和行为 并且可以修改或增加新的属性和行为 使之适合具体的需要 例如 所有的Windows应用程序都有一个窗口 它们可以看作都是从一个窗口类派生出来的 但有的应用程序用于文字处理 有的应用程序用于绘图 这是由于派生出了不同的类 它们增加了不同的属性和行为 继承很好地解决了软件的可重用性问题 4 多态性多态性是指类中具有相似功能的不同函数使用同一个名称来实现 并允许不同类的对象对同一消息作出的响应不相同 例如 同样的 编辑 粘贴 操作 在字处理程序和绘图程序中有不同的结果 同样的加法 把两个时间值相加和把两个整数相加的要求肯定不同 多态性使程序设计灵活 抽象 具有行为共享和代码共享的优点 很好地解决了程序的函数同名问题 为了支持面向对象程序设计 C 在C语言结构 struct 数据类型的基础上引入了类这种抽象数据类型 C 面向对象编程实质上就是面向类编程 只有定义和实现了类 才能声明属于这个类的对象 才能通过对象使用定义的成员 传统C程序员把编程重点放在函数的编写上 而C 程序员把重点放在类的定义和实现上 3 2C 类 C 类将对象的属性抽象为数据成员 将对象的行为抽象为成员函数 并对它们进行封装 数据成员又称成员变量 成员函数又称为方法 C 类在形式上类似于C语言中用户自定义的结构类型 但定义类时规定了成员的访问控制权限 对象只能访问所属类的公有成员 而类的私有成员只能在类的成员函数中被访问 C 类定义的基本形式 3 2 1类的定义与实现 class private public protected C 类定义的基本形式 类的定义由关键字class开始 其后为用户定义的类名 花括号括起来的部分称为类体 关键字private public和protected称为访问权限控制符 用来设置数据成员和成员函数的访问属性 其默认值为private private属性表示数据成员和成员函数是类的私有成员 它们只允许被本类的成员函数访问或调用 数据成员一般定义为private属性 说明 public属性表示数据成员和成员函数是类的公有成员 它们允许被本类或其它类的成员函数 通过对象 访问或调用 是类的外部接口 成员函数一般定义为public属性 protected属性表示数据成员和成员函数是类的保护成员 它们允许被本类的成员函数和派生类的成员函数访问或调用 例 说明 classTime private 最好不要省略privateinthour 数据成员 表示小时intminute 数据成员 表示分钟intsecond 数据成员 表示秒public voidsetTime int int int 成员函数 设置时间voidshowTime 成员函数 输出时间 例定义类Time 表示时间 私有数据成员hour minute和second只能在类的成员函数中被访问或赋值 公有成员函数setTime showTime可在外部被调用 但必须通过一个对象作为对象的成员使用 利用C 类进行面向对象编程 定义类的成员只是完成了工作的第一步 最重要的工作是实现定义的类 类的实现实质上是类的成员函数的实现 即定义类的成员函数 成员函数的定义形式与一般函数的定义形式基本相同 但必须在成员函数名前加上类名和作用域限定符 成员函数的定义也可放在类体内 该函数声明之处 这时成员函数将变成内联函数 例 类的实现 voidTime setTime inth intm ints hour h 0 例类Time的实现 private成员hour minute和second不允许外界存取 所以为类Time增加两个public成员函数 供外界设置或显示private成员 一般将类的定义放在头文件 h 中 类的实现放在源文件 cpp 中 而main主函数可以放在另一个源文件中 在源文件中用 include编译预处理指令包含头文件 C 面向对象编程约定之一 对象是类的一个实例 定义并实现了类 就可以利用定义好的类来声明对象 即创建对象 声明对象的形式与声明普通变量类似 例如 Timet1 start point pt1 t1 利用类声明对象 声明对象后 就可以通过成员运算符 或指向运算符 访问对象的公有成员 但不能访问对象的私有成员 例如 公有成员函数调用 t1 setTime start showTime pt1 setTime 而任何形如t1 hour t1 minute start second等私有成员变量的直接访问都是非法的 成员的访问 例 main TimeEndTime 声明对象EndTimeEndTime setTime 12 23 36 设置对象EndTime的时间cout Thetimeis EndTime showTime 显示对象EndTime的时间 例类Time的使用 声明对象并设置对象属性 在定义类时不能对成员变量进行初始化 因为无法确定成员变量属于哪一个对象 成员变量一般都定义为私有属性 也不能在声明对象后利用赋值运算对成员变量进行初始化 成员变量的初始化一般是利用一个名为构造函数的成员函数来完成 3 2 2构造函数和析构函数 如何进行成员变量的初始化 构造函数是一种特殊的成员函数 它是在创建对象时 声明或new动态创建 系统自动调用的成员函数 什么是构造函数 析构函数也是一种特殊的成员函数 它是在对象生存期结束时系统自动调用的成员函数 什么是析构函数 构造函数的名称与类名相同 析构函数的名称必须在类名前加上 符号 注意 构造函数和析构函数不能指定任何返回值类型 包括void返回类型 includeclassTime private inthour intminute intsecond public Time int int int 构造函数 Time 析构函数 例为类Time添加构造函数和析构函数 Time Time inth intm ints hour h 对私有成员变量初始化minute m second s cout Theconstructorbecalled hour minute second endl 构造函数和析构函数的实现 功能与成员函数Time setTime 类似 Time Time cout Thedestructorbecalled hour minute second endl voidmain void Timet1 10 35 55 自动调用构造函数Timet2 16 53 9 自动调用构造函数 退出main 主函数时自动调用析构函数 构造函数和析构函数的自动调用 程序运行结果为 Theconstructorbecalled 10 35 55Theconstructorbecalled 16 53 9Thedestructorbecalled 16 53 9Thedestructorbecalled 10 35 55 为什么是这个结果 当创建一个对象时 系统先根据类定义的成员变量为对象分配内存空间 然后自动调用对象的构造函数对这段内存空间进行初始化处理 从而完成对象的初始化 当撤消一个对象时 系统先自动调用对象的析构函数 然后释放对象所占内存空间 从程序的运行结果可以看出 析构函数的调用顺序一般与构造函数的调用顺序相反 栈 后进先出表 结果分析 与一般数据类型的变量相比 对象在它的生存期会有大量的操作 有时这些操作的结果必须在对象的生存期结束时加以清理 因此可以在析构函数中进行动态分配的内存清理工作 如果定义类时没有提供构造函数和析构函数 编译系统将会自动为类分别添加一个缺省的构造函数和析构函数 如果用户加上自定义的构造函数和析构函数 编译系统将不会再添加缺省的构造函数和析构函数 若构造函数无参数 则声明对象时也不能给出参数 补充说明 3 2 3this指针 this指针是一个特殊的隐藏在对象中的指针 每一个处于生存期的对象都有一个this指针 用于指向对象本身 当类的某个非静态成员函数被调用时 系统通过this指针确定是哪一个对象的该成员函数被调用 实际上 this指针总是作为一个隐含参数传递给类的每一个成员函数 例 下面定义的成员函数并没有声明this参数 voidTime showTime cout hour minute second endl 编译器会把this指针作为成员函数的参数 voidTime showTime Time this couthourminutesecond endl 调用时 当程序中调用某个成员函数时 编译器会把该对象的地址赋值给this指针 并将该地址值加入到参数表中 如下所示 EndTime showTime 作用 在一个成员函数中经常需要调用其它函数 非本类的成员函数 而有时需要把对象本身 即对象的地址 作为参数传递给被调用函数 这时必须使用this指针 例 例this指针的使用 include includeclassPerson public 可在外部直接访问public属性的数据成员charm strName 20 charm ID 18 public Person char strName char ID 内联构造函数 strcpy m strName strName strcpy m ID ID voidShow voidDisplay Person pObj 非成员函数 coutm strNamem IDShow 通过调用Show调用Display 3 2 4静态成员 静态成员的概念 一般情况下 同一个类不同对象的数据成员所占用的内存空间是不同的 体现了不同对象具有不同的属性值 在有些情况下 类的数据成员的值对每个对象都是相同的 如当前已创建对象的数量 这时可以将该数据成员声明为静态数据成员 占有相同的存储单元 静态成员的声明 在声明成员时以关键字static开头 例如 public staticintm nCount 说明 静态成员分为静态数据成员和静态成员函数 静态数据成员类似于一般的static静态变量 它具有全局性 静态数据成员属于整个类 为类的所有对象共享 无论类的对象有多少 类的静态数据成员只有一份 存储在同一个内存空间 即使没有创建类的一个对象 类的静态数据成员也是存在的 使用静态数据成员保证了该数据成员值的唯一性 静态成员的初始化 放在类定义的外部intPerson m nCount 0 静态成员的访问 公有静态成员 三种方式 1 通过对象访问 如 person1 m nCount 100 2 利用类名和作用域限定符 访问 如 intPerson m nCount 100 初始化 3 在成员函数中访问 如 m nCount 私有和保护静态成员 只能在成员函数中访问 静态成员函数 成员函数也可以是静态的 其声明方式与静态成员变量类似 如 public staticintGetCount 获取静态数据成员静态成员函数也与一个类相关联 而不只与一个特定的对象相关联 区别非静态成员函数 静态成员函数没有this指针 因为类的静态成员函数只有一个运行实例 成员函数一般是公有属性 可以通过对象 类名和作用域限定符 在成员函数中三种方式调用静态成员函数 静态成员函数只能访问类的静态成员 成员变量和成员函数 而不能访问类的非静态成员 因为当通过类名和运算符 调用一个静态成员函数时 不能确定函数中所访问的非静态成员属于哪一个对象 解决方法 将对象作为静态成员函数的参数 然后在静态成员函数中通过对象访问它的非静态成员 注意 例 例静态成员变量和静态成员函数的使用 include includeclassPerson public charm strName 20 longm ID staticintm nCount 静态成员变量 表示已创建对象的数量public Person char long 构造函数staticintGetCount 静态成员函数staticlongGetID Person 对象作为静态成员函数的参数 Person Person char strName longID strcpy m strName strName m ID ID m nCount 对象数目加1 intPerson GetCount returnm nCount 访问静态成员变量 longPerson GetID Personx returnx m ID 不能直接访问非静态成员m ID intPerson m nCount 0 初始化静态成员变量voidmain Persone1 LiuJun 1101051 cout Person m nCount e1 m nCount n 通过类或对象访问静态成员变量cout Person GetCount Person GetID e1 n 通过类调用静态成员函数cout e1 GetCount e1 GetID e1 n 通过对象调用静态成员函数 Persone2 WangXiaogang 1101058 cout Person GetCount Person GetID e2 n cout e2 GetCount e2 GetID e2 n cout e1 GetCount e1 GetID e1 n e1和e2共享静态成员变量m nCount 程序运行结果为 1 11 11010511 11010512 11010582 11010582 1101051 作业 P109 114 3 37 1 3 42 3 2 5友元 类具有封装性 类的私有成员一般只能通过该类的成员函数访问 这种封装性隐藏了对象的数据成员 保证了对象的安全 但有时带来了编程的不方便 友元函数 C 提供了一种函数 它虽然不是一个类的成员函数 但可以象成员函数一样访问该类的所有成员 包括私有成员和保护成员 这种函数称为友元 friend 函数 一个函数要成为一个类的友员函数 需要在类的定义中声明该函数 并在函数声明的前面加上关键字friend 友元函数本身的定义没有什么特殊要求 可以是一般函数 也可以是另一个类的成员函数 为了能够在友元函数中访问并设置类的私有数据成员 一个类的友元函数一般将该类的引用作为函数参数 例 友元函数的声明 例如 classA friendvoiddisplay A 友元函数是一个一般函数friendvoidB BMemberFun A 友元函数是另一个类B的成员函数public 友元类 友元的另一种类型是友元类 一个类可以声明另一个类为其友元类 这个友元类的所有成员函数都可以访问声明其为友元的类的所有成员 由于访问权限控制符不影响友元声明 友元声明可放在类体中任何地方 建议把友元声明放在类体的开始位置 例友元 一般友元函数 友元成员函数和友元类 的声明和使用 P78 79 例3 7 说明 友元关系是单方向的 不具有交换性和传递性 使用友元虽然简化了编程 并可避免调用成员函数的开销 但破坏了类的封装性 建议谨慎使用 作业 P114 3 43 3 3类的继承 继承是面向对象程序设计方法的四个基本特征之一 是程序代码可重用性的具体体现 在C 面向对象程序设计中 所谓类的继承就是利用现有的类创建一个新的类 新类继承了现有类的属性和行为 为了使新类具有自己所需的功能 它可以扩充和完善现有类的属性和行为 使之更具体 微软基础类MFC就是通过类的继承来体现类的可重用性和可扩充性 继承 发扬 3 3 1基类和派生类 在现实世界中 一类事物的对象常常也属于另一类事物 在面向对象程序设计方法中 一个类的对象也常常是另一个类的对象 即一个类具有了另一个类的属性和方法 在定义一个类时 根据类的继承性 我们能够且应尽可能地利用现有的类来定制新的类 而不必重新设计新的类 1 问题的提出 在继承关系中 新定义的类称为被继承类的派生类或子类 而被继承的类称为新定义类的基类或父类 派生类继承了基类的所有成员 一个派生类也可以作为另一个派生类的基类 2 基类和派生类的概念 class 派生类新增加的成员声明列表 3 派生类的定义 派生方式决定了基类的成员在派生类中的访问权限 派生方式共有三种 public private和protected 缺省值为private 虽然派生类继承了基类的所有成员 但为了不破坏基类的封装性 无论采用哪种派生方式 基类的私有成员在派生类中都是不可见的 即不允许在派生类的成员函数中访问基类的私有成员 说明 采用public派生 基类成员的访问权限在派生类中保持不变 即基类所有的公有或保护成员在派生类中仍为公有或保护成员 public派生最常用 1 可以在派生类的成员函数中访问基类的非私有成员 2 可通过派生类的对象直接访问基类的公有成员 采用private私有派生 基类所有的公有和保护成员在派生类中都成为私有成员 只允许在派生类的成员函数中访问基类的非私有成员 private派生很少使用 采用protected保护派生 基类所有的公有和保护成员在派生类中都成为保护成员 只允许在派生类的成员函数和该派生类的派生类的成员函数中访问基类的非私有成员 三种派生方式的区别 例定义类Point 然后定义类Point的派生类Circle includeclassPoint 定义基类 表示点 private intx inty public voidsetPoint inta intb x a y b 设置坐标intgetX returnx 取得X坐标intgetY returny 取得Y坐标 classCircle publicPoint 定义派生类 表示圆 private intradius public voidsetRadius intr radius r 设置半径intgetRadius returnradius 取得半径intgetUpperLeftX returngetX radius 取得外接正方形左上角的X坐标intgetUpperLeftY returngetY radius 取得外接正方形左上角的Y坐标 main Circlec c setPoint 200 250 c setRadius 100 cout X c getX Y c getY Radius c getRadius endl cout UpperLeftX c getUpperLeftX UpperLeftY c getUpperLeftY endl 公有派生类的对象可以直接访问基类Point的公有成员 程序运行结果 X 200 Y 250 Radius 100UpperLeftX 100 UpperLeftY 350 派生类Circle通过public派生方式继承了基类Point的所有成员 除私有成员外所有成员的访问权限不变 同时还定义了自己的成员变量和成员函数 若将类Circle的派生方式改为private或protected 则下述语句是非法的 c setPoint 200 250 说明 容易混淆 无论哪种派生方式 派生类都继承了基类的所有成员 包括私有成员 我们虽然不能在派生类Circle中直接访问私有数据成员x和y 但可以通过继承的公有成员函数getX getY 和setPoint 访问或设置它们 利用类继承定义类可能带来一个问题 派生类会继承它不需要的基类中的数据成员和成员函数 这时 基类中不适合于派生类的成员可以在派生类中重新加以定义 最后一个问题 例派生类成员函数对基类成员函数的覆盖 includeclassA public voidShow cout A Show n classB publicA public voidShow cout B Show n 在派生类中重新定义成员函数voidDisplay Show 调用派生类B的成员函数Show voidmain Aa 定义对象Bb 定义对象a Show 调用基类A的成员函数Show b Show 调用派生类B的成员函数Show b Display 如果想调用基类A的成员函数Show 可以使用作用域限定符 A Show 从本例可以看出 虽然派生类继承了基类的所有成员函数 但如果派生类某个成员函数的名称和参数与基类成员函数一致 即在派生类中对该成员函数重新进行了定义 则在派生类中调用的成员函数是派生类的成员函数 请问 如果在派生类B中没有对成员函数Show 重新进行定义 程序运行结果如何 程序运行结果 A ShowB ShowB Show 为什么我们经常在现有类的基础上采用继承的方法来定制新类 而不通过直接修改现有类来设计自己的类 除了代码重用的优越性 其主要原因是可能得不到基类的实现源码 继承的重要性 在利用微软基础类MFC派生自己的类时 我们只需要MFC类声明的头文件 利用 include指令将头文件包含 和含有成员函数目标代码的OBJ文件 并不需要整个MFC类库的实现源码 3 3 2基类和派生类的构造函数 一个派生类对象也属于其基类 因此当程序创建一个派生类对象时 系统首先自动创建一个基类对象 在调用派生类的构造函数构建派生类对象时 系统首先调用基类的构造函数构建基类对象 当派生类对象的生存期结束时 首先调用派生类的析构函数 然后调用基类的析构函数 1 问题的提出 编译器在对程序编译时 首先生成基类构造函数的调用代码 然后生成派生类构造函数的调用代码 隐式调用和显式调用两种方式 2 基类构造函数的调用方式 注意 除非基类有默认的构造函数 否则必须采用显式调用方式 1 隐式方式是指在派生类的构造函数中不指定对应的基类的构造函数 调用的是基类的默认构造函数 即含有缺省参数值或不带参数的构造函数 2 显式方式是指在派生类的构造函数中指定要调用的基类构造函数 并将派生类构造函数的部分参数值传递给基类构造函数 设类B是类A的派生类 则派生类B显式方式构造函数的定义形式如下 3 显式方式构造函数的定义 B B A 类B构造函数的实现代码 形参声明中的部分参数 传递给基类构造函数 派生类构造函数形参的名称和类型 派生类构造函数既初始化派生类的数据成员 又通过基类构造函数初始化其基类的数据成员 参数表中参数的个数和类型要与基类某个构造函数的形参声明一致 PointCircleCylinder 注意 当基类有多个构造函数时 编译器根据派生类构造函数为基类构造函数提供的参数表来确定调用基类的哪一个构造函数 例首先定义类Point 然后定义类Point的派生类Circle 再定义类Circle的派生类Cylinder includeclassPoint 定义基类Point protected intx y public Point inta 0 intb 0 含有缺省参数值的构造函数也是默认的构造函数x a y b cout Pointconstructor x y endl Point cout Pointdestructor x y endl classCircle publicPoint 定义类Point的派生类 protected intradius public 显式调用基类的构造函数Circle inta 0 intb 0 intr 0 Point a b radius r cout Circleconstructor radius x y endl Circle cout Circledestructor radius x y endl classCylinder publicCircle 定义类Circle的派生类 protected intheight public 显式调用基类的构造函数Cylinder inta 0 intb 0 intr 0 inth 0 Circle a b r height h cout Cylinderconstructor height radius x y endl Cylinder cout Cylinderdestructor height radius x y endl main Cylindercylinder 200 300 100 400 调用了类Point Circle和Cylinder的构造函数 Pointconstructor 200 300 Circleconstructor 100 200 300 Cylinderconstructor 400 100 200 300 Cylinderdestructor 400 100 200 300 Circledestructor 100 200 300 Pointdestructor 200 300 程序运行结果 构造函数的执行顺序 析构函数的执行顺序 Point Circle Cylinder 当声明Cylinder对象时 Cylinder Circle Point 当程序结束时 作业 P110 1143 37 2 3 45 上机 3 46 3 3 3多重继承 1 单继承和多重继承的概念 classA classB classC classA classB classC 每个派生类只有一个直接基类 单继承 一个派生类同时从多个基类派生而来 即有多个直接基类 多重继承 2 多重继承派生类的定义 设类B是类A1 A2 An的派生类 多重继承的派生类的定义形式为 class 派生类新增加的成员声明列表 多重继承的派生方式也有private public和protected三种 各基类的派生方式可以不同 例定义一个派生类MultiDerived 它是类BaseA和BaseB的派生类 classBaseA 定义基类 protected inta public voidsetA int classBaseB 定义基类 protected intb public voidsetB int 定义两个基类 classMultiDerived publicBaseA publicBaseB 定义多重继承的派生类 public intgetAB 添加成员函数 voidBaseA setA intx a x voidBaseB setB intx b x intMultiDerived getAB returna b 可以直接访问基类中protected属性成员 成员函数的实现 main MultiDerivedmd 声明派生类的对象md setA 30 调用基类BaseA的成员函数md setB 70 调用基类BaseB的成员函数cout a b md getAB endl 调用派生类MultiDerived自定义的成员函数 程序运行结果 a b 100 3 3 4虚基类 1 多重继承中的二义性问题 classC publicA public intc classD publicB publicC 类D派生于类B和类Cpublic intd main Dd1 d1 a 100 classA public inta classB publicA public intb 二义性错误 编译器无法确定数据成员a是哪一个副本 classB classC classD classA 派生类D的对象中存在间接基类A的两份副本 2 解决方法 利用作用域限定符 把基类的成员与下一层基类关联起来 d1 B a 100 或 d1 C a 100 从路径D B A继承而来 从路径D C A继承而来 缺点 浪费了存储空间 在访问基类的成员时 要求指明访问路径 大部分情况下不需要保存基类多个相同的副本 3 使用虚基类 虚基类并不是一种新的类型的类 而是一种派生方式 采用虚基类方式定义派生类 在创建派生类的对象时 类层次结构中虚基类的成员只出现一次 即基类的一个副本被所有派生类对象所共享 虚基类派生方式的定义 例采用virtual虚基类方式定义派生类 classB virtualpublicA public intb classC virtualpublicA public intc 主函数中 d1 a 100 采用虚基类方式定义派生类的方法是在基类的前面加上关键字virtual 而定义基类时与一般基类完全一样 使用虚基类派生方式的好处 节约内存空间 避免在多重派生类中类成员的不明确性 作业 P111 1143 37 3 3 48 3 4多态性和虚函数 何谓多态性 多态性也是面向对象程序设计方法的一个重要特征 它主要表现在函数调用时实现 一种接口 多种方法 两种多态性 编译时多态性和运行时多态性 编译时多态性 在函数名或运算符相同的情况下 编译器在编译阶段就能够根据函数参数类型的不同来确定要调用的函数 通过重载实现 运行时多态性 在函数名 函数参数和返回类型都相同的情况下 只能在程序运行时才能确定要调用的函数 通过虚函数实现 下节讲授的内容 3 4 1用基类指针指向派生类对象 声明一个派生类的对象的同时也自动声明了一个基类的对象 3 3小节内容派生类的对象可以认为是其基类的对象 C 允许一个基类对象的指针指向其派生类的对象 这是实现虚函数的关键不允许派生类对象的指针指向其基类的对象 即使将一个基类对象的指针指向其派生类的对象 通过该指针也只能访问派生类中从基类继承的公有成员 不能访问派生类自定义的成员 除非通过强制类型转换将基类指针转换为派生类指针 例 例基类指针与派生类指针之间的相互转换 classA private inta public voidsetA inti a i voidshowA cout a a n classB publicA private intb public voidsetB inti b i voidshowB cout b b n voidmain Aa pa pa为基类对象的指针Bb pb pb为派生类对象的指针pa 程序运行结果为 a 100b 200 pb apa setB pa showB 3 4 2虚函数 1 为什么要引入虚函数 classA public voidShow cout A Show n voidmain A pa Bb pa classB publicA public voidShow cout B Show n 如果想通过基类指针调用派生类中覆盖的成员函数 只有使用虚函数 要将一个成员函数声明为虚函数 只需在定义基类时在成员函数声明的开始位置加上关键字virtual 2 虚函数的声明 classA public virtualvoidShow cout A show n classB publicA public voidShow cout B show n voidmain Aa pa Bb pa 调用函数B Show 程序运行结果 A ShowB Show 总结 利用虚函数可以在基类和派生类中使用相同的函数名和参数类型 但定义不同的操作 这样 就为同一个类体系中所有派生类的同一类行为 其实现方法可以不同 提供了一个统一的接口 例如 在一个图形类继承结构中 设类CShape是所有具体图形类 如矩形 三角形或圆等 的基类 则函数调用语句 pShape Draw 可能是绘制矩形 也可能是绘制三角形或圆 具体绘制什么图形 取决于pShape所指的对象 3 联编的概念 即将函数调用语句与函数代码相关联 两种联编方式 静态联编和动态联编 静态联编是指编译器在编译阶段就确定了要调用的函数 即早期绑定 动态联编是指在程序执行过程中根据具体情况再确定要调用的函数 即后期绑定 重载采用静态联编方式 虽然函数名相同 但编译器能够根据函数参数类型的不同确定要调用的函数 重载体现出一种静态多态性或编译时多态性 当通过基类指针调用虚函数时 C 采用动态联编方式 虚函数体现出一种动态多态性或运行时多态性 4 构造函数 析构函数与虚函数 基于构造函数的特点 不能将构造函数定义为虚函数 声明派生类对象时自动调用基类的构造函数 例 当撤消派生类的对象时 先调用派生类析构函数 然后自动调用基类析构函数 如此看来析构函数没必要定义为虚函数 但是 假如使用基类指针指向其派生类的对象 而这个派生类对象是用new运算创建的 当程序使用delete运算撤消派生类对象时 这时只调用了基类的析构函数 而没有调用派生类的析构函数 如果使用虚析构函数 无论指针所指的对象是基类对象还是派生类对象 程序执行时都会调用对应的析构函数 例虚析构函数的使用 classA public A 构造函数不能是虚函数virtual A cout A destructor n 析构函数是虚函数 classB publicA public B B cout B destructor n 虚析构函数 voidmain A pA newB deletepA 先调用派生类B的构造函数 再调用基类A的构造函数 程序运行结果 B destructorA destructor 总结 由于使用了虚析构函数 当撤消pA所指派生类B的对象时 首先调用派生类B的析构函数 然后再调用基类A的析构函数 如果析构函数不是虚函数 则得不到下面的运行结果 结果为 A destructor 3 4 3抽象类和纯虚函数 抽象类是类的一些行为 成员函数 没有给出具体定义的类 即纯粹的一种抽象 抽象类只能用于类的继承 其本身不能用来创建对象 抽象类又称为抽象基类 抽象基类只提供了一个框架 仅仅起着一个统一接口的作用 而很多具体的功能由派生出来的类去实现 虽然不能声明抽象类的对象 但可以声明指向抽象类的指针 1 何谓抽象类 在一般的类库中都使用了抽象基类 如类CObject就是微软基础类库MFC的抽象基类 2 抽象类的定义 不定义具体实现的成员函数称为纯虚函数 纯虚函数不能被调用 仅起提供一个统一接口的作用 纯虚函数的声明 virtual 0 当基类是抽象类时 只有在派生类中重新定义基类中的所有纯虚函数 该派生类才不会再成为抽象类 一个类如果满足以下两个条件之一就是抽象类 至少有一个成员函数不定义具体的实现 定义了一个protected属性的构造函数或析构函数 3 纯虚函数 纯虚函数 例纯虚函数和抽象类的使用 定义抽象基类classCShape public doubler doubles public CShape doublex r x 声明纯虚函数virtualvoidArea 0 定义具体的派生类classCCircle publicCShape public CCircle doublex CShape x 重新定义虚函数voidArea s 3 14159 r r main CCirclecircle 48 52 circle Area cout Area circle s endl 作业 P111 1143 38 1 2 3 49 3 52 上机 3 5重载 重载是C 提供的一个新特性 C 重载分为函数重载和运算符重载 这两种重载的实质是一样的 因为进行运算可以理解为是调用一个函数 通过使用重载机制 可以对一个函数名 或运算符 定义多个函数 或运算功能 只不过要求这些函数的参数 或参加运算的操作数 的类型有所不同 重载使C 程序具有更好的可扩充性 x yX Y Add x y Add x y z 函数重载 指一组功能类似但函数参数类型 个数 不同的函数可以共用一个函数名 当C 编译器遇到重载函数的调用语句时 它能够根据不同的参数类型或不同的参数个数选择一个合适的函数 3 5 1函数重载 intabs intval returnval 0 val val floatabs floatval return val 0 val val 例通过函数参数类型的不同实现函数重载 main inti 100 cout abs i endl int型floatf 125 78F cout abs f endl float型 在程序中 求绝对值函数的名称相同 但参数类型不同 这时C 编译器自动按参数表的不同来分别联编不同的求绝对值函数 不能利用函数返回类型的不同进行函数重载 因为在没有确定调用的是哪个函数之前 不知道函数的返回类型 longabc int floatabc int 同样 不能利用引用进行函数重载 voidfun int i是一个整型变量 从上面可以看出 一般函数的重载使C 程序具有更好的可扩充性 此外 类的成员函数也可以重载 特别是构造函数的重载给C 程序设计带来很大的灵活性 例构造函数的重载 classBox private intheight width depth public Box height 0 width 0 depth 0 避免给成员变量赋不安全的值Box intht intwd intdp 重载构造函数 height ht width wd depth dp intVolume returnheight width depth voidmain Boxbox1 Boxbox2 10 15 20 cout Volume1 box1 Volume Volume2 box2 Volume endl 程序运行结果 Volume1 0 Volume2 3000 类Box有两个构造函数 第一个构造函数不带参数 把默认值0赋给对象 第二个构造函数使用参数值初始化创建的对象 3 5 2运算符重载 运算符重载 指对于不同数据类型的操作数 同一个运算符所代表的运算功能可以不同 一个运算符定义了一种操作 一个函数也定义了一种操作 其本质是相同的 当程序遇到运算符时会自动调用相应的运算符函数 虽然重载运算符完成的功能都能够用一个真正的成员函数来实现 但使用运算符重载使程序更易于理解 与函数重载类似 编译器是根据参加运算的操作数的类型来识别不同的运算 例 对于表达式 10 20编译器把它看成如下函数调用 intoperator 10 20 对于表达式 10 0 20 0编译器把它看成如下函数调用 floatoperator 10 0 20 0 参加运算的数是单精度实型数 参加运算的数是整数 我们可以将字符串operator 看成一个运算符函数名 这些同名的运算符函数根据不同类型的操作数完成不同的加法运算 重载运算符的形式 重载一个运算符 就是编写一个运算符函数 重载运算符 函数 的原型为 operator 例定义复数类型 重载运算符 运算结果的类型 要重载的运算符 参加运算的操作数 例如 c3 c1 c2 classComplex public 公有成员 以便运算符函数 非成员函数 访问floatr 实部floati 虚部public Complex floatx 0 floa
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 四川省成都市天府新区2024-2025学年八年级下期学期末考试数学试卷(含答案)
- 汉字收集资料课件
- 北师大版五年级上册数学第一单元 小数除法 检测卷(无答案)
- 2025年黑龙江省佳木斯市二十中中考数学二模试卷(含答案)
- 总承包合同(合集15篇)
- 户口申请书15篇
- “一带一路”与中国企业社会责任知到智慧树答案
- 汉字书法课件模板楷书凌
- 汉堡店加盟商业模式
- 永州市教师消防知识培训课件
- 脑水肿的诊断与治疗
- 脓毒症抗炎治疗策略
- 财务岗位招聘笔试题与参考答案
- 电动汽车V2G技术
- 田忌赛马 同步分层作业(含答案)
- 高三年级年级主任工作计划
- 2023风光互补路灯设计方案
- jgj592023安全检查标准完整版
- 关节松动技术-上肢关节松动术(运动治疗技术)
- 2024CSCO肿瘤患者静脉血栓防治指南解读
- 供应商改善计划表
评论
0/150
提交评论