C++的继承与多态
◆ 概念介绍
继承:为了代码的重用,保留基类的原本结构,并新增派生类的部分,同时可能覆盖(overide)基类的某些成员。
多态:一种将不同的特殊行为和单个泛化记号相关联的能力,分为静态多态和动态多态。
◆ 继承:
一个派生类可以通过继承获得基类的所有成员,而无需再次定义它们。分为public、protected和private三种继承方式,前两种方式保持基类的所有成员的属性不变,且派生类可以访问基类的public和protected成员,但仍然不能访问基类的private成员;private继承将使得基类的所有成员在派生类中表现为private属性。
声明一个派生类对象,即在构造派生类对象时,遵循基类的接口,先构造基类子对象,再构造派生类增加的部分。其中的组成由下图所示:

当出现菱形继承时,例如下图所示:

要构造一个SleepSofa对象,就要构造一个Sofa和一个Bed子对象,这其中又同时构造了两次Furniture对象,这是不合理的。因此Bed和Sofa类要对Furniture类进行虚继承(virtual public Furniture)来避免这种状况。
◆ 多态:
静态多态:在编译时期就已经确定了的行为,例如带变量的宏,模板,函数重载,运算符重载,拷贝构造等。
动态多态:在运行时期才能确定调用的行为。例如虚函数调用机制。本部分主要讨论的是动态多态。虚函数是实现动态多态的机制,其核心理念就是通过基类指针来访问派生类定义的成员。成员函数在基类为虚函数时,在派生类同样也是虚函数。纯虚函数是指不希望基类对象调用的成员函数,需要派生类覆盖实现这样的纯虚函数。(注:如果某个成员函数在基类中没有用virtual关键字修饰,即普通函数,而在派生类中却又有完全相同的成员函数声明,两个函数即使有相同的名字和相同的参数类型与数量,这两个函数也是完全不同的函数,因为类的作用域不同)
虚函数表(vtable):每个类都拥有一个虚函数表,虚函数表中罗列了该类中所有虚函数的地址,排列顺序按声明顺序排列,例如这样两个类
class Base
{
virtual void f() {}
virtual void g() {}
//其他成员
};
Base b;

class Derive : public Base
{
void f() {}
virtual void d() {}
//其他成员
};
Derive d;

虚表指针(vptr):每个类有一个虚表指针,当利用一个基类的指针绑定基类或者派生类对象时,程序运行时调用某个虚函数成员,会根据对象的类型去初始化虚指针,从而虚表指针会从正确的虚函数表中寻找对应的函数进行动态绑定,因此可以达到从基类指针调用派生类成员的效果。
那么为什么需要虚指针和虚函数表来实现动态多态呢?因为无论是什么函数,包括类内的虚函数和非虚函数,都会储存在内存中的代码段。但是当编译器在编译时,就可以确定普通函数和非虚函数的入口地址,以及其调用的信息,所以这指的是常量指针。当遇到动态多态时,虚函数真正的入口地址的指针要在运行时根据对象的类型才能确定,所以要通过虚指针从虚函数表中找虚函数对应的入口地址。
当然,用基类指针绑定的子类对象,只能通过这个基类指针调用基类中的成员,因为作用域仅限于基类的子对象,子类新增的部分是看不见的。
总结为下面这个例程:
#include <iostream> using std::cout;
using std::endl; class Base
{
public:
void fun() { cout << "Base::fun()" << endl; }
virtual void vfun() { cout << "Base::virtual fun()" << endl; }
}; class Derive : public Base
{
public:
void fun() { cout << "Derive::fun()" << endl; }
virtual void vfun() { cout << "Derive::virtual fun()" << endl; }
void dfun() { cout << "Derive::dfun()" << endl; }
}; int main()
{
Base* bp = new Base();
Base* dp = new Derive(); bp->fun();
bp->vfun(); dp->fun();
dp->vfun();
//dp->dfun(); //编译错误:基类指针指向子类中基类的子对象
//不能看到子类的成员 delete bp;
delete dp; return ;
}
输出为:

可以看出,bp绑定一个基类对象,调用自己的成员无异议;dp绑定的是一个子类对象,因此调用fun()时,由于dp是一个基类指针,作用域在于基类中,所以调用的是基类的fun(),而调用vfun()是通过动态绑定调用虚函数表中被子类覆盖的Derive::vfun(),而如果要调用dfun()时则会出现编译错误,因为子类独有成员基类指针不可见。
注:在解有关动态多态的题时,只要把握住一点:这个指针指向的到底是基类对象还是子类对象,如果是基类对象,则调用基类的成员函数,如果是子类对象,则要考虑到这个虚成员函数是否被子类中的成员覆盖掉,即是否产生了动态绑定。另外还有一点,从子类对象强制类型转换为基类对象是允许的,而相反地要从基类对象强制转换成子类对象是错误的(编译不通过)。
Base* dp1 = new Derive();
Derive* dp2 = (Derive*) dp1; //基类指针指向的是子类对象,可以强制转化为子类指针 Base* bp1 = new Base();
Derive* bp2 = (Base*) bp1; //错误,[Error] invalid conversion from 'Base*' to 'Derive*' [-fpermissive]
//基类指针指向的是基类对象,不能强制转化为子类指针
C++的继承与多态的更多相关文章
- Objective-C中的继承和多态
面向对象编程之所以成为主流的编程思想和他的继承和多态是分不开的,只要是面向对象语言都支持继承和多态,当然不同的OOP语言之间都有其特点.OC中和Java类似,不支持多重继承,但OOP语言C++就支持多 ...
- java中抽象、分装、继承和多态的理解
1.抽象.封装装.继承和多态是java面向对象编程的几大特点. 抽象:所谓抽象就是对某件事务,我们忽略我们不关心不需要的部分,提取我们想要的属性和行为,并且以代码的形式提现出来:例如我们需要对一个学生 ...
- [转] JS中简单的继承与多态
这里讲了一个最最最简单的JS中基于原型链的继承和多态. 先看一下以下这段代码的实现(A是“父类”,B是“子类”): var A = function(){ this.value = 'a'; this ...
- 网络电视精灵~分析~~~~~~简单工厂模式,继承和多态,解析XML文档,视频项目
小总结: 所用技术: 01.C/S架构,数据存储在XML文件中 02.简单工厂模式 03.继承和多态 04.解析XML文档技术 05.深入剖析内存中数据的走向 06.TreeView控件的使用 核心: ...
- OC的封装、继承与多态
面向对象有三大特征:封装.继承和多态. 一.封装 封装是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问.简而言之,信息隐藏,隐 ...
- 2、C#面向对象:封装、继承、多态、String、集合、文件(上)
面向对象封装 一.面向对象概念 面向过程:面向的是完成一件事情的过程,强调的是完成这件事情的动作. 面向对象:找个对象帮你完成这件事情. 二.面向对象封装 把方法进行封装,隐藏实现细节,外部直接调用. ...
- Java学习笔记 07 接口、继承与多态
一.类的继承 继承的好处 >>使整个程序架构具有一定的弹性,在程序中复用一些已经定义完善的类不仅可以减少软件开发周期,也可以提高软件的可维护性和可扩展性 继承的基本思想 >>基 ...
- JavaScript 面向对象程序设计(下)——继承与多态 【转】
JavaScript 面向对象程序设计(下)--继承与多态 前面我们讨论了如何在 JavaScript 语言中实现对私有实例成员.公有实例成员.私有静态成员.公有静态成员和静态类的封装.这次我们来讨论 ...
- Java继承和多态实例
我们知道面向对象的三大特性是封装.继承和多态.然而我们有时候总是搞不清楚这些概念.下面对这些概念进行整理, 为以后面向抽象的编程打下坚实的基础. 封装的概念还是很容易理解的.如果你会定义类,那么相信你 ...
- python基础——继承和多态
python基础——继承和多态 在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类.父类或超类 ...
随机推荐
- 【转载】COM 组件设计与应用(六)——用 ATL 写第一个组件
原文:http://vckbase.com/index.php/wv/1216.html 一.前言 1.与 <COM 组件设计与应用(五)>的内容基本一致.但本回讲解的是在 vc.net ...
- Kubernetes学习之路(五)之Flannel网络二进制部署和测试
一.K8S的ip地址 Node IP:节点设备的IP,如物理机,虚拟机等容器宿主的实际IP. Pod IP:Pod的IP地址,是根据docker0网络IP段进行分配的. Cluster IP:Serv ...
- UWP 五星评价(不跳转到龟速商店)
之前写过一篇文章 UWP 五星好评 代码如下 var pfn = Package.Current.Id.FamilyName; await Launcher.LaunchUriAsync(new ...
- python 排序模块 ———— heapq(学习笔记)
from heapq import * def heasort(initi):# 排序 h=[] for value in initi: heappush(h,value)#将每一个item进入hea ...
- monkey测试入门1
Monkey是一款通过命令行来对我们APP进行测试的工具,可以运行在模拟器里或真机上.它向系统发送伪随机的用户事件流,实现对正应用程序进行压力测试. 官方介绍 :https://developer.a ...
- 相机标定与矫正opencv+MATLAB
博客转载自:http://blog.csdn.net/Loser__Wang/article/details/51811347 本文目的在于记录如何使用MATLAB做摄像机标定,并通过opencv进行 ...
- 使用过滤器解决JSP页面的乱码问题
乱码详情 总结:讨论了使用GET和POST方法,控制台和JSP页面显示的问题. 最终发现:在servlet或者过滤器中添加:request.setCharacterEncoding("ut ...
- golang应用打包成docker镜像
golang编译的应用是不需要依赖其他运行环境的,那么为什么还需要打包成docker镜像呢?当需要附带配置和日志等文件时可以更方便的移植和运行,下面介绍从dockerfile编译成镜像. 在项目根目录 ...
- 使用C#采集Shibor数据到Excel
对Shibor的变化一直以来比较关注,正好最近学习了对html数据处理的一些知识,就打算拿来采集一些我需要的Shibor数据. 使用到的库 HttpAgilityPack 一个非常不错的html解析工 ...
- Homebrew1.5之后安装PHP和扩展
Homebrew 1.5 宣布放弃 homebrew/php, 转而使用homebrew/core维护, 详见https://brew.sh/2018/01/19/homebrew-1.5.0/ 于是 ...