cpp交通十:组合与继承
发布日期:2021-06-29 18:53:17 浏览次数:2 分类:技术文章

本文共 4457 字,大约阅读时间需要 14 分钟。

文章目录

  • 面向对象的重要特征
    • 代码重用

  • 两方法可完成这任务
  • 用已有类的对象作为新定义类的数据成员。
    • 新的类是已有类的对象组合而成,
    • 称为组合。
  • 在一个已存在类的基础上,对它扩展,形成一个新类。
    • 称继承
  • 继承是面向对象程序设计的重要思想,
    • 也是运行时多态性的实现基础

10.2继承

  • 继承创建新类时,需指明这个新类是在哪个已有类的基础上扩展
    • 基类,派生类
  • 派生类本身也可能会成为未来派生类的基类
  • 如果派生类只有一个基类,则称为单继承
  • 派生类是从多个基类派生出来的,这些基类之间可能毫无关系,则称为多继承
    • 多继承用得少,不介绍

  • 派生类在基类基础上添一些数据成员和成员函数,
    • 通常比基类大得多
  • 基类和派生类之间是普遍和特殊

10.2.1定义派生类

  • 派生类定义格式:

    class派生类名:继承方式 基类名
    {新增加的成员声明;}

  • 继承方式: public、 private和 protected

    • 说明基类的成员在派生类中的访问特性
    • 默认private
  • 新增加的成员声明是派生类在基类基础上扩充

  • Derived在类Base基础上增加一个数据成员和一个成员函数
  • Derived有两数据成员,两成员函数

  • 公有成员能被所有函数访问
    • 私有成员只能被自己的成员函数和友元访问
  • 有了继承后,第三种访问特性protected
    • 介于 public 和 private间
  • protected成员是一类特殊的私有成员
    • 不可被全局函数或其他类成员函数访
    • 但能被派生类的成员函数和友元函数访

  • 派生类包含基类的所有成员
    • 这些成员到了派生类后的访问特性是什么
  • 派生类的成员函数不能直接访问基类的私有成员
    • 用派生类对象的程序当然更不能用基类的私有成员
  • 其他成员的访问特性取决于它在基类中的访问特性和继承方式。
  • 继承方式有3种:
    • 从基类public派生某类
    • 基类的public成员会成为派生类的public成员
    • 基类的protected成员会成为派生类的protected成员
    • 从基类protected派生一个类时,基类的 public成员和 protected成员都成为派生类的protected成员
    • 从基类 private派生一个类时,基类的 public成员和 protected成员成为派生类的 private成员

  • 继承时采用的继承方法都是 public
    • 它可在派生类中保持基类的访问特性。
    • 另外两很少用

  • 派生类对象中包含一个基类对象及自己新增部分
    • 初始化派生类对象需同时初始化这两部分
  • 基类的数据成员往往私有
    • 派生的成员函数没权利访问这些私有成员
    • 构造函数也不例外
  • C++规定
    • 派生类对象的初始化由基类和派生类共同完成
  • 派生类构造函数体只初始化新增加的数据成员
    • 派生类在初始化列表中调用基类的构造函数初始化基类的数据成员
  • 派生类构造函数的一般如下

10.3运行时的多态性

  • 向不同人发同一令

    • 不同人有不同反应
  • 寝室长督促同一寝室的去上课

  • 每个同学都知道该去哪上课

    • 寝室长只需对所有同学说“该上课去”
    • 每位同学都会去自己该去的教室
  • 这就是多态性

  • 两整型数和两实型数发同样加法指令

    • 整型数和实型数在机器内的表示方法不一样的
    • 将两个整型数相加和将两个实型数相加的过程不一样,
    • 机器会调不同函数完成相应功能

  • 有了多态性,
    • 相当于对象有主观能动性
  • 对对象的使用者而言,使用起来就方便
  • 当被管理的对象类型增加时,管理者工作量不增加。
  • 对寝室长而言,1
  • 个寝室有4个同学和一个寝室有10个同学时的工作量一样,
    • 他都只说一句“该上课去了”
  • 寝室长的工作相当于main函数工作。
  • 只要增加对象类型,main函数不变,
    • 但系统功能得到扩展

  • 面向对象程序设计中
  • 多态性有两实现方式
    • 编译时多态性(静态绑定)
    • 运行时多态性(动态绑定)。
  • 9章运算符重载
    • 属编译时多态性
  • 运算符重载
  • 程序用同样指令
    • 如对整型变量x,y执行x+y
    • 对有理数类的对象r1,r2执行rl+r2
    • 编译器会对这两个表达式调不同函数
  • 本节 : 运行时多态性实现
    • 同一条指令到底调哪函数要到执行到这一条指令时才能决定
  • 运行时的多态性
    • 通过“继承”机制实现

10.3.1派生类对象到基类对象的隐式转换

  • C++要类A的对象隐式转换成类B的对象
    • 须在类A中定义一个向类B转换的类型转换函数
  • 派生类对象到基类对象的转换
    • 不需定义类型转换函数
  • C++默认派生类对象到基类对象的转换是保留派生类中的基类部分
  • 派生类到基类的隐式转换出现3场合
    • 把派生类对象赋给基类
    • 把派生类对象地址赋给基类指针
    • 定义一个基类的引用类型对象,
      • 引用的是派生类对象

1)将派生类对象赋给基类对象

  • 派生类中包含一个基类的对象,
    • 就是把派生类中的基类部分赋给此基类对象。
    • 派生类新增加的成员就舍弃
  • 赋值后,基类对象和派生类对象再无关系

2)基类指针指向派生类对象

  • 让基类指针指向派生类对象时,
    • 尽管该指针指向的对象是一个派生类对象,
    • 但它本身基类指针,它只能解释基类成员,
      • 不能解释派生类新增成员
  • 从指向派生类的基类指针出发,
    • 只能访问派生类中的基类部分
  • 清单10-13
  • 定义一个派生类对象,并对此对象调display
    • 派生类的display重定义了基类的 display函数
    • 覆盖了基类的display函数
    • 派生类对象调用到的是派生类的display函数
  • 基类的 display函数是存在的,只是派生类的对象看不见。
    • 当用一个基类的指针去指向派生类的对象时,
      • 尽管这块空间中有两个数据成员,
      • 但基类指针只看得见x
    • 尽管这个类有两个display函数,但基类指针只看得见基类
      的 display函数
  • 对此基类指针调display,
    • 调用到的是基类的 display

3)基类的对象引用派生类的对象

  • 引用事实上是一种隐式的指针。
  • 定义一个基类的引用类型对象,引用的是一个派生类对象时,
    • 相当于给派生类中的基类部分取一个别名,
    • 从该基类对象看到的是派生类对象中的基类部分。
  • br引用的是d中基类部分。
  • 对br的访问就是对d中的基类部分的访问,对br的修改就
    是对d中基类部分的修改

  • 派生类对象可隐式转换成基类对象,
    • 基类对象无法隐式转换成派生类对象
    • 除非在基类中定义了一个向派生类转换的类型转换函数,才能将基类对象转换成派生类对象。
  • 不能将基类对象的地址赋给派生类的指针,
    • 即使某个基类指针指向的就是一个派生类的对象
  • dp=bp时,编译器报错。

  • 能确信该基类指针指向的是一个派生类的对象,
    • 确实想把这个基类指针赋给派生类的指针,
    • 用强制类型转换
  • 告诉编译器:
    • 我知道这个危险,但我保证不会出问题。
  • reinterpret cast是危险的转换,
    • 它让系统按程序员的意思解释内存信息

10.3.2虚函数与多态性

  • 基类指针或引用
    • 可指向派生类对象
    • 可访问派生类对象中的基类部分,
    • 不能访问派生类新增成员。
  • 基类某函数被定义为虚函数
    • 表明该函数在派生类中可能有不同实现
    • 用基类的指针调该虚函数时,
      • 先到派生类中去看一看这个函数有没有重新定义
    • 如果派生类重新定义这个函数,
      • 则执行派生类中的函数
      • 否则执行基类函数

  • 类定义中的函数原型声明前加virtual。
  • 派生类中重新定义时,
    • 函数原型必须与基类中的虚函数完全同,
    • 否则编译器会认为派生类有两个重载函数
  • 从该基类派生出多个派生类时,
    • 每个派生类都可重新定义这个虚函数
  • 用基类指针指向不同的派生类的对象时,
    • 就调不同函数
    • 就实现多态性
  • 这个绑定要到运行时根据当时基类指针指向的是哪一个派生类的对象,
    • 才能决定调用哪一个函数
  • 运行时多态性

  • 定义一个 Shape类记录任意形状的位置,并定义一个计算面积的函数和显示形状及位置的函数,
    • 都是虚函数
  • 派生Rectangle类和Circle类
    • 这两个类都有可以计算面积和显示形状及位置的函数
  • 矩形和圆计算面积的方法以及显示的方法都是不同的,因此必须重写基类的这两个函数。

  • 基类指针指向的是一个派生类对象。
  • 基类中area和 display都是虚函数,
    • 通过基类指针找到基类中的这两个函数时,
    • 会到派生类中去检査有没有重新定义。
  • Rectangle类中重新定义了这两个函数,
    • 执行Rectangle类中的函数

  • 用虚函数时,注意
    • 派生类中重新定义虚函数时
      • 必须与基类中的虚函数完全相同
      • 否则编译器把它认为是重载函数,
      • 而不是虚函数重定义
  • 派生类在对基类虚函数重定义时,
    • virtual可以写也可以不写。
    • 不管virtual写还是不写,该函数都被认为是虚函数。
    • 最好重定义时写上

10.3.3虚析构函数

  • 构造函数不能是虚函数
  • 析构函数可以是
    • 最好是虚函数

  • 派生类新增加的数据成员中含有指针,
    • 指向动态内存
    • 那派生类须定义析构函数释放这空间
  • 如果派生类的对象通过基类指针操作
    • 则delete基类指针指向的派生类的对象时
    • 就内存泄漏
  • 当基类指针指向的对象析构时
    • 通过基类指针会找到基类的析构函数
    • 执行基类析构函数
  • 但此时派生类动态申请的空间没有释放,要释放这块空间必须执行派生类析构函数。

也就是析构函数最好是虚函数!!

  • 可将基类的析构函数定义为虚函数
  • 当析构基类指针指向的派生类的对象时
    • 先找到基类析构函数
    • 基类的析构函数是虚函数,又找到派生类的析构函数,执行派生类析构函数。
  • 派生类的析构函数执行时会自动调基类析构函数
    • 基类和派生类的析构函数都被执行
    • 就把派生类的对象完全析构
    • 不只析构派生类中的基类部分

  • 与其他虚函数一样
    • 析构函数的虚函数性质都将被继承
  • 如果继承层次树中的根类的析构函数是虚函数的话
    • 所有派生类的析构函数都将是虚函数

10.4纯虚函数和抽象类

10.4.1纯虚函数

  • 例10.7中Shape类,只表示具有封闭图形的东西,
    • 但谈起图形时,会讲到圆、三角形、矩形等,
    • 但没有种图形叫“形状”
    • 这个类中定义一个area函数没意义
  • 之所以在Shape类中定义这个函数,是为了实现多态性
  • 为表示这种函数,C++引人了纯虚函数

  • 纯虚函数是一个在基类中声明的虚函数
  • 它在基类中没有定义函数体
  • 纯虚函数声明形式

  • 不用为Shape类中的area函数写一个无用的函数体,
    • 只需把它声明为纯虚函数:

10.4.2抽象类

  • 一个类至少含一个纯虚函数
    • 抽象类

知道啥叫抽象类了吧!

  • Shape被定义成一个抽象类

  • 抽象类中有些函数没有函数体
    • 不能定义抽象类的对象
    • 一旦对此对象调用纯虚函数,该函数无法执行
  • 可定义指向抽象类的指针
    • 作用是指向派生类对象
    • 以实现多态

  • 如果抽象类的派生类没有重新定义此纯虚函数,
    • 只是继承基类的纯虚函数,
    • 那派生类仍是一个抽象类

  • 抽象类保证进入继承层次的每个类都具有纯虚函数所要求的行为
    • 这保证围绕这个继承层次所建立起来的类
    • 具有抽象类规定的行为
    • 避免这个继承层次中的用户由于偶尔的失误(忘了为它所建立的派生类提供继承层次所要求的行为)而影响系统正常运行

转载地址:https://cyj666.blog.csdn.net/article/details/108319063 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:find找东西啦!和which的作用
下一篇:cpp交通:八:创建新的类型

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2024年04月24日 07时30分23秒