详解C++ 多态的实现及原理

C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数

1:用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。

2:存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。

3:多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。

4:多态用虚函数来实现,结合动态绑定.

5:纯虚函数是虚函数再加上 = 0;

6:抽象类是指包括至少一个纯虚函数的类。

纯虚函数:virtual void fun()=0;即抽象类!必须在子类实现这个函数,即先有名称,没有内容,在派生类实现内容。

下面看下c++语言虚函数实现多态的原理

自上一个帖子之间跳过了一篇总结性的帖子,之后再发,今天主要研究了c++语言当中虚函数对多态的实现,感叹于c++设计者的精妙绝伦

c++中虚函数表的作用主要是实现了多态的机制。首先先解释一下多态的概念,多态是c++的特点之一,关于多态,简而言之就是 用父类的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数,这种方法呢,可以让父类的指针具有多种形态,也就是说不需要改动很多的代码就可以让父类这一种指针,干一些很多子类指针的事情,这里是从虚函数的实现机制层面进行研究

在写这篇帖子之前对于相关的文章进行了查阅,基本上是大段的文字,所以我的这一篇可能会用大量的图形进行赘述(如果理解有误的地方,烦请大佬能够指出),接下来就言归正传:

首先介绍一下为什么会引进多态呢,基于c++的复用性和拓展性而言,同类的程序模块进行大量重复,是一件无法容忍的事情,比如我设置了苹果,香蕉,西瓜类,现在想把这些东西都装到碗这个函数里,那么在主函数当中,声明对象是必须的,但是每一次装进碗里对于水果来说,都要用自己的指针调用一次装的功能,那为什么不把这些类抽象成一个水果类呢,直接定义一个水果类的指针一次性调用所有水果装的功能呢,这个就是利用父类指针去调用子类成员,但是这个思想受到了指针指向类型的限制,也就是说表面指针指向了子类成员,但实际上还是只能调用子类成员里的父类成员,这样的思想就变的毫无意义了,如果想要解决这个问题,只要在父类前加上virtual就可以解决了,这里就是利用虚函数实现多态的实例。

首先还是作为举例来两个类,在之前基础知识的帖子中提到过,空类的大小是一个字节(占位符),函数,静态变量都在编译期就形成了,不用类去分配空间,但是做一个小实验,看一看在定义了虚函数之后,类的大小是多少呢

#include<iostream>
using namespace std;
class CFather
{
public:
 virtual void AA()  //虚函数标识符
 {
 cout << "CFather :: AA()" << endl;
 }
 void BB()
 {
 cout << "CFather :: BB()" << endl;
 }
};
class CSon : public CFather
{
public:
 void AA()
 {
 cout << "CSon :: AA()" << endl;
 }
 void BB()
 {
 cout << "CSon :: BB()" << endl;
 }
};
int main()
{
 cout << sizeof(CFather) << endl;       //测试加了虚函数的类
 system("pause");
 return 0;
}

很明显类里装了一个 4个字节的东西,除了整形int,就是指针了,没错这里装的就是函数指针

先把这个代码,给抽象成图形进行理解,在这CFather为A,CSon为B

此时就是一个单纯的继承的情况,不存在虚函数,然后我new一个对象,A *p = new A;那么 p -> AA(),必然是指向A类中的AA()函数,那么函数的调用有两种方式 一种函数名加()直接调用,一种是利用函数指针进行调用,在这里我想要调用子类的,就可以利用函数指针进行调用,假设出来两个函数指针,来指向B类中的两个成员函数,如果我父类想要调用子类成员,就可以通过 p指针去调用函数指针,再通过函数指针去调用成员函数

每一个函数都可以用一个函数指针去指着,那么每一类中的函数指针都可以形成自己的一个表,这个就叫做虚函数表

那么在创建对象后,为什么类中会有四个字节的内存空间呢?

在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象中最前面的位置(这是为了保证正确取到虚函数的偏移量)。这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。也就是说这四个字节的指针,代替了上图中(p->*pfn)()的作用,指向了函数指针,也就是说,在使用了虚函数的父类成员函数,虽然写的还是p->AA(),实际上却是,(p->*(vfptr[0])),而指向哪个虚函数表就由,创建的对象来决定

至此,就能理解如何用虚函数这个机制来实现多态的了

下面,我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

无虚数覆盖

下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,Derive d; 的虚函表:

我们可以看到下面几点:

1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。

有虚数覆盖

覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

我们从表中可以看到下面几点,

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

2)没有被覆盖的函数依旧。

这样,我们就可以看到对于下面这样的程序,

Base *b = new Derive();

b->f();

由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

总结

以上所述是小编给大家介绍的c++语言虚函数实现多态的原理,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

(0)

相关推荐

  • C++中继承与多态的基础虚函数类详解

    前言 本文主要给大家介绍了关于C++中继承与多态的基础虚函数类的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 虚函数类 继承中我们经常提到虚拟继承,现在我们来探究这种的虚函数,虚函数类的成员函数前面加virtual关键字,则这个成员函数称为虚函数,不要小看这个虚函数,他可以解决继承中许多棘手的问题,而对于多态那他更重要了,没有它就没有多态,所以这个知识点非常重要,以及后面介绍的虚函数表都极其重要,一定要认真的理解~ 现在开始概念虚函数就又引出一个概念,那就是重写(覆

  • 深入解析C++中的虚函数与多态

    1.C++中的虚函数C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有"多种形态",这是一种泛型技术.所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法.比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议. 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Tab

  • C++中的多态与虚函数的内部实现方法

    1.什么是多态 多态性可以简单概括为"一个接口,多种行为". 也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可以用自己的方式去响应共同的消息.所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数.这是一种泛型技术,即用相同的代码实现不同的动作.这体现了面向对象编程的优越性. 多态分为两种: (1)编译时多态:主要通过函数的重载和模板来实现. (2)运行时多态:主要通过虚函数来实现. 2.几个相关概念 (1)覆盖.

  • 详解C++ 多态的实现及原理

    C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数.如果对象类型是派生类,就调用派生类的函数:如果对象类型是基类,就调用基类的函数 1:用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数. 2:存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针.虚表是和类对应的,虚表指针是和对象对应的. 3:多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态

  • 详解C++虚函数的工作原理

    静态绑定与动态绑定 讨论静态绑定与动态绑定,首先需要理解的是绑定,何为绑定?函数调用与函数本身的关联,以及成员访问与变量内存地址间的关系,称为绑定. 理解了绑定后再理解静态与动态. 静态绑定:指在程序编译过程中,把函数调用与响应调用所需的代码结合的过程,称为静态绑定.发生在编译期. 动态绑定:指在执行期间判断所引用对象的实际类型,根据实际的类型调用其相应的方法.程序运行过程中,把函数调用与响应调用所需的代码相结合的过程称为动态绑定.发生于运行期. C++中动态绑定 在C++中动态绑定是通过虚函数

  • 详解Java 中泛型的实现原理

    泛型是 Java 开发中常用的技术,了解泛型的几种形式和实现泛型的基本原理,有助于写出更优质的代码.本文总结了 Java 泛型的三种形式以及泛型实现原理. 泛型 泛型的本质是对类型进行参数化,在代码逻辑不关注具体的数据类型时使用.例如:实现一个通用的排序算法,此时关注的是算法本身,而非排序的对象的类型. 泛型方法 如下定义了一个泛型方法, 声明了一个类型变量,它可以应用于参数,返回值,和方法内的代码逻辑. class GenericMethod{ public <T> T[] sort(T[]

  • 详解Java线程池和Executor原理的分析

    详解Java线程池和Executor原理的分析 线程池作用与基本知识 在开始之前,我们先来讨论下"线程池"这个概念."线程池",顾名思义就是一个线程缓存.它是一个或者多个线程的集合,用户可以把需要执行的任务简单地扔给线程池,而不用过多的纠结与执行的细节.那么线程池有哪些作用?或者说与直接用Thread相比,有什么优势?我简单总结了以下几点: 减小线程创建和销毁带来的消耗 对于Java Thread的实现,我在前面的一篇blog中进行了分析.Java Thread与内

  • 详解vue的数据binding绑定原理

    自从angular火了以后,各种mvc框架喷涌而出,angular虽然比较火,但是他的坑还是蛮多的,还有许多性能问题被人们吐槽.比如坑爹的脏检查机制,数据binding是受人喜爱的,脏检查就有点-性能低下了.有时候改了一个地方,脏循环要循环多次来保证数据是不是真的变了和是否停止变化了.这样性能就很低了.于是人们开始钻研新的双向数据binding的方法.尤大的vue binding就是本人蛮喜欢的一种实现方式,本文跟随尤大的一个例子来详解vue的数据binding的原理. 数据binding,一般

  • 详解c# 多态

    多态是同一个行为具有多个不同表现形式或形态的能力. 多态性意味着有多重形式.在面向对象编程范式中,多态性往往表现为"一个接口,多个功能". 多态性可以是静态的或动态的.在静态多态性中,函数的响应是在编译时发生的.在动态多态性中,函数的响应是在运行时发生的. 在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自 Object. 多态就是同一个接口,使用不同的实例而执行不同操作,如图所示: 现实中,比如我们按下 F1 键这个动作: 如果当前在 Flash 界面下弹出

  • 详解C++ 多态的两种形式(静态、动态)

    1.多态的概念与分类 多态(Polymorphisn)是面向对象程序设计(OOP)的一个重要特征.多态字面意思为多种状态.在面向对象语言中,一个接口,多种实现即为多态.C++中的多态性具体体现在编译和运行两个阶段.编译时多态是静态多态,在编译时就可以确定使用的接口.运行时多态是动态多态,具体引用的接口在运行时才能确定. 静态多态和动态多态的区别其实只是在什么时候将函数实现和函数调用关联起来,是在编译时期还是运行时期,即函数地址是早绑定还是晚绑定的.静态多态是指在编译期间就可以确定函数的调用地址,

  • 详解SpringMVC的url-pattern配置及原理剖析

    xml里面配置标签: <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> &

  • 详解ES6中class的实现原理

    一.在ES6以前实现类和继承 实现类的代码如下: function Person(name, age) { this.name = name; this.age = age; } Person.prototype.speakSomething = function () { console.log("I can speek chinese"); }; 实现继承的代码如下:一般使用原型链继承和call继承混合的形式 function Person(name) { this.name =

  • 详解Vue3 Teleport 的实践及原理

    Vue3 的组合式 API 以及基于 Proxy 响应式原理已经有很多文章介绍过了,除了这些比较亮眼的更新,Vue3 还新增了一个内置组件: Teleport.这个组件的作用主要用来将模板内的 DOM 元素移动到其他位置. 使用场景 业务开发的过程中,我们经常会封装一些常用的组件,例如 Modal 组件.相信大家在使用 Modal 组件的过程中,经常会遇到一个问题,那就是 Modal 的定位问题. 话不多说,我们先写一个简单的 Modal 组件. <!-- Modal.vue --> <

随机推荐