当前课程知识点:基于Linux的C++ >  第九讲 类与对象 >  9.10 继承(二) >  LinuxCPP0910

返回《基于Linux的C++》慕课在线视频课程列表

LinuxCPP0910在线视频

LinuxCPP0910

下一节:LinuxCPP0911

返回《基于Linux的C++》慕课在线视频列表

LinuxCPP0910课程教案、知识点、字幕

继承的时候有可能导致基类的成员函数

和派生类的成员函数具有同名的情况

在这种情况下边就有可能发生函数的覆盖

调用的时候有可能会发生二义性

我们在编程的时候必须要能够处理它

我们看这样的一个例子

class Point 里面有一个函数 假设叫Print

而在Point3D里边

它不仅继承了Point类的Print函数

它自己又实现了一个

这实际上意味着Point3D这个对象

具有两个Print函数 一个是它自己的

一个是它从基类继承下来的

这两个函数在Point3D这样的对象上边

怎么访问呢

pt是Point类的一个对象

当我们用pt.Print来调用它的时候

我们实际上调用的是Point这个类的

那个Print函数

当我们在pt3d.Print调用这个Print的时候

注意pt3d因为是Point3D类的一个对象pt3d的话

那么它的调用

是调用Point3D这个类的Print成员函数

它自己内部的那个实现

它不会调用Point那个类的Print函数

也就是继承下来的那个函数它没有被调用

缺省的时候它调用它自己实现的那个Print函数

如果你真地想在Point3D类里边

去调用它基类的那个Print函数

那么你就必须明确地指定那个Print函数

是从属于基类的 而不属于派生类的特点

你得必须把这个特征给我体现出来

怎么办呢 要用名解析的方式

所以要写pt3d.Point::Print

很怪异的写法 但它能够工作

就能够在Point3D类的对象里边正确地调用

从基类Point类里边继承下来的那个Print函数

而不是调用它自己的那个Print函数

这一点在继承函数中

需要调用基类的那个对应成员函数

去做基础处理的时候尤为重要

在派生类需要调用基类的同名函数

去做一些基础处理的时候

显得尤其如此

单继承 它描述了类库的基本组织方式

理论上来讲 它其实能够解决任何问题

但是C++本身

它还提供了特殊的多继承的机制

也就是说 一个派生类

可以从好几个基类中同时继承数据

这个就叫多继承

多继承的基本的语法格式就是这个:

class派生类的名字

冒号后边跟着逗号分隔开的不同的基类

每一个基类都有一个对应的

派生类型的保留字

确定它到底是按照什么样的方式去派生

同样地 public、protect、private

三个格式去派生

每一个基类都可以不一样

你比如说 我们这样的例子

我定义一个ClassA 又定义一个classB

然后定义一个classC

公有派生A 保护派生B

这样的话 C类将具有

A类和B类的全部的属性和行为

在C类里边 A类的public成员还是public

A类protected成员还是protected

A类的private看不见

而在C类里边 从B类继承下来的

那些public的东西和protected的东西

全变成了protected

而B类private成员

同样地在C类里面还是看不见

这就是多重继承

多重继承导致的问题

比它能够带来的好处还要多

所以我们一定要慎用

就像我刚才讲的

理论上一个单继承能够解决我们任何问题

所以使用多继承在绝大多数情况下

其实是毫无必要的

所以我们不建议同学们

在实际编程的时候使用多继承的机制

它导致什么问题呢 它导致的问题就是

一个派生类里面

可能包含很多个基类的副本

这些基类副本的位置

你没有办法去决定它

因为对于一个单继承来讲

它一个基类的一个数据对象

它的存储布局是基本是这样一个模式:

按照它的数据成员的顺序

一个接着一个分配好 放在那个地方

构造这个对象的时候

就按照这个方式来构造

当我派生的时候

首先会继承基类这些数据成员

它的架构 包括它的存储布局

一模一样地全都继承下来

然后在后边继续添加自己的数据

这就形成一个非常明确的

不断扩展的数据存储布局

这是一个单继承的模式

如果多继承

那么它的第二个基类就只能写在

第一个基类的后边

第三个基类就写在第二个基类的后边

这个存储布局 你看上去也是顺序的

但是如果它(注:指基类)有多个副本

就可能导致它(注:指派生类)

在后续某个位置还有基类的数据

访问的时候是非常讨厌的

操作起来很麻烦 一不注意就能搞错

所以大部分情况下面

理论上我们应该慎用它

我们看这个例子

我定义classA 定义classB

从A公有派生 定义class C

从A类公有派生 从B类保护派生

这个时候 在C类里边

上来就会放A类的一个存储空间

接下来放B类的存储空间

B类的存储空间是什么呢

B类的存储空间上来就是A类的存储空间

然后是B类新添的数据成员

在此之后 才是C类自己定义的数据成员

你看到了吧

这里有A类的数据成员的两个副本

而且第一个副本是公有派生下来的

第二个副本是保护派生下来的

访问控制还不一样 所以在这种情况下

取的是A类第一个副本呢还是第二个副本呢

你编程的时候就需要特别小心

一不注意就容易搞错 另外那我还要问了

保存A类的两个副本有意义吗

大部分情况下边

在C类保存A类的两个副本是没有意义的

所以多继承问题很多

在这一点上面尤其需要注意

当采用多继承的时候

派生类的那个成员函数名字

和原来的函数(基类成员函数)的名字

相同的情况更多了

所以使用的时候也需要注意

解析方式和以前类似

但是会更麻烦一点

我们看一个例子 classA有一个f函数

classB有一个f函数

classC从A类公有继承、从B类公有继承

然后自己又写了一个f函数

你注意到这里面有几个f函数

三个 A类一个 B类一个 C类一个

那么c.f这样的一个调用

调用的就是c的f函数

如果是c.A::f 调用的就是C类

从A类继承下来的那个函数

如果c.B::f

调用的就是从B类继承的那个函数

所以名解析一样是可以工作

但是肯定就不像原来那么方便

就在这儿 你看这个例子

A类f函数 B类从A类公有派生f函数

C类从A类公有派生f函数

D类从B、C类公有派生f函数

这里面多少个f函数啊 太多了

A类有一个 B类有一个 C类有一个

D类有几个 D类自己定义了一个

从B类继承了几个 从B类继承了两个

因为B类本身有A类一个 自己的一个

从C类继承了几个 从C类继承了两个

那么你想调用它的时候 d.f

调用的就是D类f函数

d.B::f 调用的就是从B类继承下来的f函数

d.C::f 调用的就是从C类继承下来的f函数

d.B::A::f 调用的就是从B类继承的

A类的那个f函数

沿着它那个继承的树往上回溯

找到它对应的f函数

才能够明确地解析出来它

这样才能够准确地调用

当然在大部分情况下边

我从B类和C类继承的A的f函数有差别吗

当然没差别 有意义吗

很多时候 这么写其实是没有意义的

我们刚才讲了

一旦在一个多继承的机制下面

派生类就可能保留基类的很多个副本

大部分情况下它是无效的 没有意义的

那么我们怎么限制这一点

让它只保留基类的唯一一个副本呢

后续的同样的副本我都删掉 我不要了

那我们怎么保证这一点呢

那么我们就需要虚继承

取消继承时派生类中公共基类的多个副本

只保留唯一的一份就叫虚继承

派生的时候填上virtual这样一个关键字

就能够保证它是虚继承的

classA 这是一个类的定义

classB从A类虚继承 classC从A类虚继承

D类从B、C类继承

D类不需要写virtual public B,virtual public C

virtual写在virtual public A里边

就表示B类的这一个类的对象上

所有的A类的副本只有一个

C类的上面 所有的A类的副本只有一个

所以当D类里边既从B类继承

又从C类继承的时候

那么它首先继承B类 保留一个A类的副本

当它再继承C类的时候

发现D类这个对象已经有了一个A类的副本

那么它的自己A类的那个副本它就不要了

这个就是虚继承的目的

整个D类将只有A类的一个副本

而不会有很多个

virtual和public这种写法 前后颠倒没关系

它只和多继承有关 对于单继承

因为所有的继承都是单一的一条线

所以它不会产生多继承的情况

基类的副本不会有两个

那么虚继承就不需要

你写了虚继承 从C++的实现上来讲

它需要做一些额外的操作

而这个额外的操作实际上

是会降低我们程序的效率的

所以在单继承模式下

你可以认为虚基类这种东西压根就不重要

不存在 不要管它

基于Linux的C++课程列表:

第一讲 C/C++基本语法元素

-1.1 提纲

--LinuxCPP0101

-1.2 程序设计的基本概念

--LinuxCPP0102

-1.3 简单C/C++程序介绍

--LinuxCPP0103

-1.4 程序设计的基本流程

--LinuxCPP0104

-1.5 基本语法元素

--LinuxCPP0105

-1.6 程序设计风格

--LinuxCPP0106

-1.7 编程实践

--LinuxCPP0107

-第一讲 C/C++基本语法元素--编程实践提交入口

第二讲 程序控制结构

-2.1 提纲

--LinuxCPP0201

-2.2 结构化程序设计基础

--LinuxCPP0202

-2.3 布尔数据

--LinuxCPP0203

-2.4 分支结构

--LinuxCPP0204

-2.5 break语句

--LinuxCPP0205

-2.6 循环结构

--LinuxCPP0206

-2.7 编程实践

--LinuxCPP0207

-第二讲 程序控制结构--编程实践提交入口

第三讲 函数

-3.1 提纲

--LinuxCPP0301

-3.2 函数声明、调用与定义

--LinuxCPP0302

-3.3 函数调用栈框架

--LinuxCPP0303

-3.4 编程实践

--LinuxCPP0304

-第三讲 函数--编程实践提交入口

第四讲 算法

-4.1 提纲

--LinuxCPP0401

-4.2 算法概念与特征

--LinuxCPP0402

-4.3 算法描述

--LinuxCPP0403

-4.4 算法设计与实现

--LinuxCPP0404

-4.5 递归算法(一)

--LinuxCPP0405

-4.6 递归算法(二)

--LinuxCPP0406

-4.7 容错与计算复杂度

--LinuxCPP0407

-4.8 编程实践

--LinuxCPP0408

-第四讲 算法--编程实践提交入口

第五讲 程序组织与开发方法

-5.1 提纲

--LinuxCPP0501

-5.2 库与接口

--LinuxCPP0502

-5.3 随机数库(一)

--LinuxCPP0503

-5.4 随机数库(二)

--LinuxCPP0504

-5.5 作用域与生存期

--LinuxCPP0505

-5.6 典型软件开发流程(一)

--LinuxCPP0506

-5.7 典型软件开发流程(二)

--LinuxCPP0507

-5.8 编程实践

--LinuxCPP0508

-第五讲 程序组织与开发方法--编程实践提交入口

第六讲 复合数据类型

-6.1 提纲

--LinuxCPP0601

-6.2 字符

--LinuxCPP0602

-6.3 数组(一)

--LinuxCPP0603

-6.4 数组(二)

--LinuxCPP0604

-6.5 结构体

--LinuxCPP0605

-6.6 编程实践

--LinuxCPP0606

-第六讲 复合数据类型--编程实践提交入口

第七讲 指针与引用

-7.1 提纲

--LinuxCPP0701

-7.2 指针基本概念

--LinuxCPP0702

-7.3 指针与函数

--LinuxCPP0703

-7.4 指针与复合数据类型(一)

--LinuxCPP0704

-7.5 指针与复合数据类型(二)

--LinuxCPP0705

-7.6 字符串

--LinuxCPP0706

-7.7 动态存储管理(一)

--LinuxCPP0707

-7.8 动态存储管理(二)

--LinuxCPP0708

-7.9 引用

--LinuxCPP0709

-7.10 编程实践

--LinuxCPP0710

-第七讲 指针与引用--编程实践提交入口

第八讲 链表与程序抽象

-8.1 提纲

--LinuxCPP0801

-8.2 数据抽象(一)

--LinuxCPP0802

-8.3 数据抽象(二)

--LinuxCPP0803

-8.4 链表(一)

--LinuxCPP0804

-8.5 链表(二)

--LinuxCPP0805

-8.6 链表(三)

--LinuxCPP0806

-8.7 链表(四)

--LinuxCPP0807

-8.8 函数指针(一)

--LinuxCPP0808

-8.9 函数指针(二)

--LinuxCPP0809

-8.10 抽象链表(一)

--LinuxCPP0810

-8.11 抽象链表(二)

--LinuxCPP0811

-8.12 编程实践

--LinuxCPP0812

-第八讲 链表与程序抽象--编程实践提交入口

第九讲 类与对象

-9.1 提纲

--LinuxCPP0901

-9.2 程序抽象与面向对象

--LinuxCPP0902

-9.3 类类型

--LinuxCPP0903

-9.4 对象(一)

--LinuxCPP0904

-9.5 对象(二)

--LinuxCPP0905

-9.6 类与对象的成员(一)

--LinuxCPP0906

-9.7 类与对象的成员(二)

--LinuxCPP0907

-9.8 类与对象的成员(三)

--LinuxCPP0908

-9.9 继承(一)

--LinuxCPP0909

-9.10 继承(二)

--LinuxCPP0910

-9.11 继承(三)

--LinuxCPP0911

-9.12 多态(一)

--LinuxCPP0912

-9.13 多态(二)

--LinuxCPP0913

-9.14 编程实践

--LinuxCPP0914

-第九讲 类与对象--编程实践提交入口

第十讲 操作符重载

-10.1 提纲

--LinuxCPP1001

-10.2 四则运算符重载(一)

--LinuxCPP1002

-10.3 四则运算符重载(二)

--LinuxCPP1003

-10.4 关系与下标操作符重载

--LinuxCPP1004

-10.5 赋值操作符重载(一)

--LinuxCPP1005

-10.6 赋值操作符重载(二)

--LinuxCPP1006

-10.7 赋值操作符重载(三)

--LinuxCPP1007

-10.8 赋值操作符重载(四)

--LinuxCPP1008

-10.9 赋值操作符重载(五)

--LinuxCPP1009

-10.10 流操作符重载(一)

--LinuxCPP1010

-10.11 流操作符重载(二)

--LinuxCPP1011

-10.12 流操作符重载(三)

--LinuxCPP1012

-10.13 操作符重载总结

--LinuxCPP1013

-10.14 编程实践

--LinuxCPP1014

-第十讲 操作符重载--编程实践提交入口

第十一讲 泛型编程

-11.1 提纲

--LinuxCPP1101

-11.2 泛型编程概览

--LinuxCPP1102

-11.3 异常处理机制(一)

--LinuxCPP1103

-11.4 异常处理机制(二)

--LinuxCPP1104

-11.5 运行期型式信息(一)

--LinuxCPP1105

-11.6 运行期型式信息(二)

--LinuxCPP1106

-11.7 模板与型式参数化

--LinuxCPP1107

-11.8 题外话:术语翻译

--LinuxCPP1108

-11.9 泛型编程实践(一)

--LinuxCPP1109

-11.10 泛型编程实践(二)

--LinuxCPP1110

-11.11 泛型编程实践(三)

--LinuxCPP1111

-11.12 泛型编程实践(四)

--LinuxCPP1112

-11.13 泛型编程实践(五)

--LinuxCPP1113

-11.14 泛型编程实践(六)

--LinuxCPP1114

-11.15 泛型编程实践(七)

--LinuxCPP1115

-11.16 泛型编程实践(八)

--LinuxCPP1116

-11.17 泛型编程实践(九)

--LinuxCPP1117

-11.18 泛型编程实践(十)

--LinuxCPP1118

-11.19 编程实践

--LinuxCPP1119

-第十一讲 泛型编程--编程实践提交入口

第十二讲 Linux系统编程基础

-12.1 提纲

--LinuxCPP1201

-12.2 程序执行环境(一)

--LinuxCPP1202

-12.3 程序执行环境(二)

--LinuxCPP1203

-12.4 程序执行环境(三)

--LinuxCPP1204

-12.5 程序执行环境(四)

--LinuxCPP1205

-12.6 输入输出(一)

--LinuxCPP1206

-12.7 输入输出(二)

--LinuxCPP1207

-12.8 文件系统

--LinuxCPP1208

-12.9 设备

--LinuxCPP1209

-12.10 库(一)

--LinuxCPP1210

-12.11 库(二)

--LinuxCPP1211

-12.12 makefile文件(一)

--LinuxCPP1212

-12.13 makefile文件(二)

--LinuxCPP1213

-12.14 makefile文件(三)

--LinuxCPP1214

-12.15 编程实践

--LinuxCPP1215

-第十二讲 Linux系统编程基础--编程实践提交入口

第十三讲 进程编程

-13.01 提纲

--LinuxCPP1301

-13.02 进程基本概念

--LinuxCPP1302

-13.03 信号

--LinuxCPP1303

-13.04 进程管理(一)

--LinuxCPP1304

-13.05 进程管理(二)

--LinuxCPP1305

-13.06 进程管理(三)

--LinuxCPP1306

-13.07 进程间通信(一)

--LinuxCPP1307

-13.08 进程间通信(二)

--LinuxCPP1308

-13.09 进程间通信(三)

--LinuxCPP1309

-13.10 进程间通信(四)

--LinuxCPP1310

-13.11 进程池

--LinuxCPP1311

-13.12 编程实践

--LinuxCPP1312

-第十三讲 进程编程--编程实践提交入口

第十四讲 线程编程

-14.1 提纲

--LinuxCPP1401

-14.2 线程基本概念

--LinuxCPP1402

-14.3 线程管理(一)

--LinuxCPP1403

-14.4 线程管理(二)

--LinuxCPP1404

-14.5 线程管理(三)

--LinuxCPP1405

-14.6 线程管理(四)

--LinuxCPP1406

-14.7 线程同步机制(一)

--LinuxCPP1407

-14.8 线程同步机制(二)

--LinuxCPP1408

-14.9 C++11线程库(一)

--LinuxCPP1409

-14.10 C++11线程库(二)

--LinuxCPP1410

-14.11 C++11线程库(三)

--LinuxCPP1411

-14.12 C++11线程库(四)

--LinuxCPP1412

-14.13 C++11线程库(五)

--LinuxCPP1413

-14.14 编程实践

--LinuxCPP1414

-第十四讲 线程编程--编程实践提交入口

第十五讲 网络编程

-15.1 提纲

--LinuxCPP1501

-15.2 Internet网络协议

--LinuxCPP1502

-15.3 套接字(一)

--LinuxCPP1503

-15.4 套接字(二)

--LinuxCPP1504

-15.5 编程实践

--LinuxCPP1505

-第十五讲 网络编程--编程实践提交入口

课程文档

-课程PDF文件

LinuxCPP0910笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。