当前课程知识点:基于Linux的C++ > 第九讲 类与对象 > 9.9 继承(一) > LinuxCPP0909
接下来一节是继承
这一节包括六个主题:
一个是继承与派生的基本概念
一个是单继承 一个是多继承
第四个是虚继承
第五个是派生类的构造函数和析构函数
第六个是类的赋值兼容性
首先我们来看继承与派生的基本概念
类类型本身描述的是分类的一个概念
我们在解决一个实际问题的时候
将这个问题所需要操纵的数据对象
分解成了一个又一个的类
这是面向对象程序设计中
非常重要的一个地方
分解出来的这些类
有些是没有多少关系的
有些是有紧密关系的
你比如讲 我要写一个程序
来处理我们的屋子里面的那些物品
比如说像桌子 桌子有很多种
一个餐桌是一个桌子
我们的写字台是一个桌子
我们的床头柜从理论上来讲
它应该也是一个桌子
所有的这些 它们的功用和属性
可能是不一样的
你使用一个类来描述是不妥当的
有些属性在梳妆台上是没有的
有些属性是在餐桌上所没有的
如果你用一个类来描述它
就意味着所有的这些属性
都必须全部地包含进去
而使用的时候 有些对象你只能用这部分
有些对象只能用那一部分
你怎么控制和管理这一点呢
控制和管理不了的
所以只使用单一的类来描述是不对的
我们要为餐桌声明一个类
我们要为梳妆台声明一个类
我们要为写字台声明一个类
你如果把这三个类完全地孤立开
认为它们之间没关系 这也是不对的
它们从广义的角度来讲都应该是桌子
所以我们的抽象应该更近一步
我们要把所有的这些类共性都抽取出来
形成一个抽象的桌子
同时餐桌、写字台、床头柜
都从原来那个桌子那个地方继承下来
这是一个非常重要的一个概念
继承就构造了我们整个类库的
各个类之间的层次关系
所谓继承就体现在这里
它描述了类之间的血缘关系
餐桌是从一个抽象的桌子继承下来的
所以我们说餐桌就是那个桌子的派生类
而那个抽象的桌子就是基类
有时候我们也说桌子是父类
而餐桌是子类 这个概念不太妥当
因为在类库层次下
有的时候我们说子类可能是另外一个词
叫做subclass 翻译出来也叫子类
它这个写法和它的意义
和我们讲继承的时候这个子类是不一样的
所以为了不引起混淆
同学们最好是使用基类和派生类这个概念
一个叫base 一个叫derived
来描述类的继承的层次
也就是说 在解决实际问题的时候
我声明了这么多类 我们既把它分解开
同时又形成继承的层次
这样的话 构造的类库的框架
它是立体的 它才能够完整地描述
我们现实世界中的问题
因为我们现实世界中的事物
事实上是普遍联系的
它们互相之间
是构成一个复杂的网状连接关系的
所以没有继承 面向对象技术
它实际上就是不完整的
功能是极度受限的
继承 同学们一定要了解
那么在C++代码里边
继承所形成的那个派生类
将拥有基类的全部属性和行为
一个基类所拥有的
到了派生类里边全部都有
想取消是不可能的
这是一个非常重要的地方
只能添加新的功能 不能取消已有的功能
派生类可以增加新的属性和行为
而不能删除原有的属性和行为
这是继承本身特殊的要求
这保证我们的程序代码不会误操作
因为一旦把这个函数真删除了
你后续的代码万一想调用它
那么你实际上就调用不到了
这是非常重要的一个地方
首先我们来看单继承
单继承的基本模式是这样的:class
派生类的名字
冒号后边跟着一个派生类型的保留字
然后后面跟着一个基类的类型
其后是花括号对 后面跟着分号
表明这个类的定义结束
在派生类类型的保留字里面
可以有public 可以有protected 可以有private
这个同样是访问控制
它决定按照什么样的方式继承基类的特性
如果你的派生类型是public
我们称之为它是公有派生
它表示在这个派生类里边
从基类继承下来的全部属性和行为
维持着它原来的访问控制不变
那就意味着从基类中继承下来的属性
尽可能地保留了基类的访问控制规则
原来是public的 还是public
原来是protected还是protected
原来是private 那是它的隐私
你是不能看的
派生类是没有权利访问基类的私有数据的
如果是个protected的继承机制
那么基类的private当然是不可见的
除此之外 public、protected的
所有的这些成员
在派生类中都自动变成protected
如果是一个私有派生 private
那么这就意味着基类的private成员
你是看不见的
而基类的public成员或者protected成员
在我们的派生类里边都自动地变成private
当你进一步继承的时候 它们就不可见了
就在孙子类里边就看不见了
所以真实继承的时候
大部分情况下边我们使用的都是public
公有继承
而不是保护继承或私有继承
平时用的以public居多
在设计类的时候
因为我们需要频繁地在派生类里边
访问基类的数据对象
如果每次都是使用存取函数
那个基类里提供的那个公有的存取函数
来访问基类的那个对象
很多时候它是不方便的
即使那些函数被你定义成内联的
它也可能是不方便的
所以在这种情况下边
我们在定义类的层次的时候
为了解决问题的方便
不用private定义基类的私有数据
而是使用protected定义基类的数据
把那个数据定义成保护的
同时我们公有派生
保证在整个类的继承树的下边
后续的所有的派生类
不管它是多少层的派生类
都能够访问最基础的那一个类的protected成员
非常重要的一点 这是一个编程习惯
从某种情况来讲
它打破了这个类的私有规则
这个方式不是完美无缺的
它实际上是有一点小问题的
可是为了解决问题的方便
很多时候我们习惯于这么做
而这么做也确实能够带给我们方便
我们看这样一个例子
定义一个类:class Point
它的公有的构造函数 有存取函数Get/Set
还有一些特定的重载的友元函数 operator<<
(重载了一个输出操作符)
这个友元函数呢
具体的实现 我们现在不解释
我们重载操作符的时候
下一讲会再讨论它
我们看其它的几个函数
这些都是公有的 最重要的是在这里:
protected成员_x和_y
_x和_y被我们定义成protected
而不是private
这就意味着这两个成员是保护成员
而不是私有成员
Point这个类本身
它是所有点类的一个基类
我们可以在这个点类上边定义二维点
定义三维点 一个二维平面点
一个三维立体空间点
甚至还有更多维的超平面的点
你都可以在此基础上定义
显然不管它是几维的点 它都是点
所以二维点、三维点
所有的这些点其实都应该
从一个基本的点类里边继承下来
但是我们刚才那个实现
因为已经有了x和y坐标
所以实际上是一个二维点的基类
所以此后我们可以在这个二维点基类上边
封装一个第三维 形成一个三维点
真正地全抽象的一个点
当然应该是一个0维的点才恰当
我们才可以在此基础上
继承出来形成一维点
继承出来形成二维点
继承出来形成三维点
应该按照这样的一个继承层次
我们这里面只是一个示例
所以我们定义一个新的Point3D类
它是一个三维的数据点
我们从Point这个类里边
公有继承下来:public Point
这里边定义好 同时也实现好它的构造函数
Point3D两个构造函数 GetZ、SetZ:
添加的这个字段的存取函数
然后重载操作符“<<”
输出操作符又被我们重载了
因为针对二维点的输出和针对三维点的输出
实际上是不一样的
前面四个函数都被我们内联了
就只有重载的这个流操作符我们没有内联
所以在“.cpp”里面
我们要实现这两个操作符的重载函数
当然Point3D这个点
它的那个保护字段_z被我们添加进去了
实现的这个代码其实很简单
就在operator<<函数内部
按照小括号对这个格式
中间用逗号分隔开 输出它的二维点
输出它的三维点 把它输出就完了
只是这个函数的实现因为涉及到流操作
涉及到操作符的重载
我们现在还没有办法对它进行详细解释
我们下一讲会讨论怎么重载这些操作符
那个时候我们才会详细解释它
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口