◆ 概念介绍

继承:为了代码的重用,保留基类的原本结构,并新增派生类的部分,同时可能覆盖(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++的继承与多态的更多相关文章

  1. Objective-C中的继承和多态

    面向对象编程之所以成为主流的编程思想和他的继承和多态是分不开的,只要是面向对象语言都支持继承和多态,当然不同的OOP语言之间都有其特点.OC中和Java类似,不支持多重继承,但OOP语言C++就支持多 ...

  2. java中抽象、分装、继承和多态的理解

    1.抽象.封装装.继承和多态是java面向对象编程的几大特点. 抽象:所谓抽象就是对某件事务,我们忽略我们不关心不需要的部分,提取我们想要的属性和行为,并且以代码的形式提现出来:例如我们需要对一个学生 ...

  3. [转] JS中简单的继承与多态

    这里讲了一个最最最简单的JS中基于原型链的继承和多态. 先看一下以下这段代码的实现(A是“父类”,B是“子类”): var A = function(){ this.value = 'a'; this ...

  4. 网络电视精灵~分析~~~~~~简单工厂模式,继承和多态,解析XML文档,视频项目

    小总结: 所用技术: 01.C/S架构,数据存储在XML文件中 02.简单工厂模式 03.继承和多态 04.解析XML文档技术 05.深入剖析内存中数据的走向 06.TreeView控件的使用 核心: ...

  5. OC的封装、继承与多态

    面向对象有三大特征:封装.继承和多态. 一.封装 封装是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问.简而言之,信息隐藏,隐 ...

  6. 2、C#面向对象:封装、继承、多态、String、集合、文件(上)

    面向对象封装 一.面向对象概念 面向过程:面向的是完成一件事情的过程,强调的是完成这件事情的动作. 面向对象:找个对象帮你完成这件事情. 二.面向对象封装 把方法进行封装,隐藏实现细节,外部直接调用. ...

  7. Java学习笔记 07 接口、继承与多态

    一.类的继承 继承的好处 >>使整个程序架构具有一定的弹性,在程序中复用一些已经定义完善的类不仅可以减少软件开发周期,也可以提高软件的可维护性和可扩展性 继承的基本思想 >>基 ...

  8. JavaScript 面向对象程序设计(下)——继承与多态 【转】

    JavaScript 面向对象程序设计(下)--继承与多态 前面我们讨论了如何在 JavaScript 语言中实现对私有实例成员.公有实例成员.私有静态成员.公有静态成员和静态类的封装.这次我们来讨论 ...

  9. Java继承和多态实例

    我们知道面向对象的三大特性是封装.继承和多态.然而我们有时候总是搞不清楚这些概念.下面对这些概念进行整理, 为以后面向抽象的编程打下坚实的基础. 封装的概念还是很容易理解的.如果你会定义类,那么相信你 ...

  10. python基础——继承和多态

    python基础——继承和多态 在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类.父类或超类 ...

随机推荐

  1. 3235: [Ahoi2013]好方的蛇

    3235: [Ahoi2013]好方的蛇 链接 分析: 可以求出以每个点为顶点的满足条件的矩形有多少个,单调栈求.设为sum. 然后对这个数组进行二维前缀和,可以求出每个矩阵内,以右下角.左下角为端点 ...

  2. P4284 [SHOI2014]概率充电器

    P4284 [SHOI2014]概率充电器 今天上课讲到的题orz,第一次做这种上下搞两次dp的题. g[i]表示i的子树(包括i)不给i充电的概率. f[i]表示i的父亲不给i充电的概率. g[]可 ...

  3. 处于同一域中的两台SQL Server 实例无法连接

    处于同一个域中的两台Sql server 实例无法连接,报的错误信息如下: A network-related or instance-specific error occurred while es ...

  4. 什么是Gradle

    一.什么是Gradle 简单的说,Gradle是一个构建工具,它是用来帮助我们构建app的,构建包括编译.打包等过程.我们可以为Gradle指定构建规则,然后它就会根据我们的“命令”自动为我们构建ap ...

  5. 使用Nmon_Analyzer excel 问题总结

    使用wps打开nmon的分析文件,出现  运行时错误13类型不匹配 查看具体代码,是这句出现错误Start = DateValue(Sheet1.Range("date")),进一 ...

  6. Java 中的接口

    接口概念 接口可以理解为一种特殊的类,由全局常量和公共抽象方法组成.类是一种具体实现体,而接口定义了某一些类所要遵守的规范,接口不必关心这些类的内部数据,也不关心这些类里方法的实现细节,它只规定这些类 ...

  7. SQL Server Management Studio 键盘快捷键

    光标移动键盘快捷键 操作 SQL Server 2012 SQL Server 2008 R2 左移光标 向左键 向左键 右移光标 向右键 向右键 上移光标 向上键 向上键 下移光标 向下键 向下键 ...

  8. 快速稀疏角点光流框架(Fast sparse corner optical flow framework)

    光流适用在连续的图像系列(视频流)中,描述本身或画面目标的运动状态:在目标跟踪.运动分析.甚至slam中都有广泛应用. opencv里就有不少光流算法,其中很经典也是当前被调用最多的的Lucas-Ka ...

  9. 深入理解C/C++二维数组

    深入理解C/C++二维数组 前言 本来以为自己对二维数组的理解还可以,没感觉有什么,但是今天小伙伴问了一个问题感觉迷惑了好久,于是决定细致的记录一下,一步一步的探究各种关于二维数组的问题,巩固基础. ...

  10. Linux系统中时间区域和API

    1.问题 在开发云平台程序的时候,经常会碰到时间区域转换的问题.比如,任何网络存储的文档的metadata都自己记录了编辑时间.但是,云平台记录时需要把这个时间转成标准时间,便于管理.但是用户使用的时 ...