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称为基类.父类或超类 ...
随机推荐
- Kubernetes学习之路(十三)之Pod控制器--DaemonSet
一.什么是DaemonSet? DaemonSet 确保全部(或者一些)Node 上运行一个 Pod 的副本.当有 Node 加入集群时,也会为他们新增一个 Pod .当有 Node 从集群移除时,这 ...
- c# table 怎么在前台循环展示 ViewBag
后台 public ActionResult DoctorEvaluation()//前台页面 { HE_Department HE_dt = new HE_Department(); DataTab ...
- [转载]在Windows下为PHP5.6安装redis扩展和memcached扩展
一.php安装redis扩展 1.使用phpinfo()函数查看PHP的版本信息,这会决定扩展文件版本 2.根据PHP版本号,编译器版本号和CPU架构, 选择php_redis-2.2 ...
- node的 node-sass@^4.11.0 出现:npm: no such file or directory, scandir '.../node_modules/node-sass/vendor'
解决办法: 查看node_modules文件夹,发现,并无vender 文件夹.如下图: 2. 在 node_modules/node-sass 下创建 vendor 文件夹 3. 最后运行: n ...
- WordPress函数wp_page_menu详解
说明 该标签显示带有链接的WordPress页面列表,并且可以选择将 Home(主页)自动显示为列表中的一员.该标签是自定义侧边栏和标题栏的好帮手,同时还可以用在其它模板中. WordPress教程 ...
- Docker创建容器
容器是镜像的一个运行实例,是基于镜像运行的轻量级环境,是一个或者一组应用. 怎样创建容器?将容器所基于的镜像名称传入即可,Docker会从本地仓库中寻找该镜像,如果本地仓库没有,则会自动从远程仓库中拉 ...
- 从零开始的Python学习Episode 21——socket基础
socket基础 网络通信要素: A:IP地址 (1) 用来标识网络上一台独立的主机 (2) IP地址 = 网络地址 + 主机地址(网络号:用于识别主机所在的网络/网段.主机号:用于识别该网络中的 ...
- PHP.ini 能不能加载子配置文件 ?
答案是不能,php这个地方用的是另一个方案解决的 编译的时候 用这个参数 --with-config-file-scan-dir指定一个目录 然后在这个目录里面加载ini https://www. ...
- exec命令详解
基础命令学习目录首页 原文链接: exec: 在bash下输入man exec,找到exec命令解释处,可以看到有”No new process is created.”这样的解释,这就是说exec命 ...
- CentOS 6.7 安装配置 nagios
一.简介 Nagios是一款开源的免费网络监视工具,能有效监控Windows.Linux和Unix的主机状态,交换机路由器等网络设置,打印机等.在系统或服务状态异常时发出邮件或短信报警,第一时间 ...