C++面向对象总结——虚指针与虚函数表

最近在逛B站的时候发现有候捷老师的课程,如获至宝。因此,跟随他的讲解又复习了一遍关于C++的内容,收获也非常的大,对于某些模糊的概念及遗忘的内容又有了更深的认识。
以下内容是关于虚函数表、虚函数指针,而C++中的动态绑定实现和这两个内容是分不开的。
一,虚函数表、虚指针
当一个类在实现的时候,如果存在一个或以上的虚函数时,那么这个类便会包含一张虚函数表。而当一个子类继承并重写了基类的虚函数时,它也会有自己的一张虚函数表。
当我们在设计类的时候,如果把某个函数设置成虚函数时,也就表明我们希望子类在继承的时候能够有自己的实现方式;如果我们明确这个类不会被继承,那么就不应该有虚函数的出现。
下面是某个基类A的实现:
class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1, m_data1;
};
从下图中可以看到该类在内存中的存放形式,对于虚函数的调用是通过查虚函数表来进行的,每个虚函数在虚函数表中都存放着自己的一个地址,而如何在虚函数表中进行查找,则是通过虚指针来调用,在内存结构中它一般都会放在类最开始的地方,而对于普通函数则不需要通过查表操作。这张虚函数表是什么时候被创建的呢?它是在编译的时候产生,否则这个类的结构信息中也不会插入虚指针的地址信息。

以下例子包含了继承关系:
class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1, m_data1;
};
class B : public A {
public:
virtual void vfunc1();
void func2();
private:
int m_data3;
};
class C : public B {
public:
virtual void vfunc1();
void func2();
private:
int m_data1, m_data4;
};
以上三个类在内存中的排布关系如下图所示:

- 对于非虚函数,三个类中虽然都有一个叫 func2 的函数,但他们彼此互不关联,因此都是各自独立的,不存在重载一说,在调用的时候也不需要进行查表的操作,直接调用即可。
- 由于子类B和子类C都是继承于基类A,因此他们都会存在一个虚指针用于指向虚函数表。注意,假如子类B和子类C中不存在虚函数,那么这时他们将共用基类A的一张虚函数表,在B和C中用虚指针指向该虚函数表即可。但是,上面的代码设计时子类B和子类C中都有一个虚函数 vfunc1,因此他们就需要各自产生一张虚函数表,并用各自的虚指针指向该表。由于子类B和子类C都对 vfunc1 作了重载,因此他们有三种不同的实现方式,函数地址也不尽相同,在使用的时候需要从各自类的虚函数表中去查找对应的 vfunc1 地址。
- 对于虚函数 vfunc2,两个子类都没有进行重载操作,所以基类A、子类B和子类C将共用一个 vfunc2,该虚函数的地址会分别保存在三个类的虚函数表中,但他们的地址是相同的。
- 从上图可以发现,在类对象的头部存放着一个虚指针,该虚指针指向了各自类所维护的虚函数表,再通过查找虚函数表中的地址来找到对应的虚函数。
- 对于类中的数据而言,子类中都会包含父类的信息。如上例中的子类C,它自己拥有一个变量 m_data1,似乎是和基类中的 m_data1 重名了,但其实他们并不存在联系,从存放的位置便可知晓。
二,关于动态绑定
首先来说一说静态绑定:静态绑定是指在程序编译过程中,把函数(方法或者过程)调用与响应调用所需的代码结合的过程(如何理解呢?)
来看一段代码:
#include <iostream>
using namespace std; class Shape {
protected:
int width, height;
public:
Shape(int a,int b):width(a),height(b){}
int area()
{
cout << "Parent class area :" << endl;
return 0;
}
};
//将Rectangle类继承Shape类
class Rectangle : public Shape {
public:
Rectangle(int a,int b) :Shape(a, b) { }
int area()
{
cout << "Rectangle class area :" <<width*height<< endl;
return 0;
}
}; // 程序的主函数
int main()
{
Shape* shape;//定义shpae类指针
Rectangle rec(10, 7);//派生类对象
// 基类指针指向派生类对象(存储矩形的地址)
shape = &rec;
// 调用矩形的求面积函数 area
shape->area();
return 0;
}

可以看到调用的却是派生类的函数。
在没有加virtual关键字的时候,通过基类指针指向派生类对象时,基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数。这是因此在系统编译过程中,已经将area()函数和shape类绑定在一起了。
而动态绑定是在加了virtual关键字以后,派生类中的成员函数在重写的时候会自动生成自己的虚函数表(单独的一个地址),并通过虚指针指向该地址。
即:shape指针->vptr->Rectangle::area()
通过以上内容,我们可以知道在使用基类指针调用虚函数的时候,它能够根据所指的类对象的不同来正确调用虚函数。而这些能够正常工作,得益于虚指针和虚函数表的引入,使得在程序运行期间能够动态调用函数。
动态绑定有以下三项条件要符合:
- 使用指针进行调用
- 指针属于up-cast后的
- 调用的是虚函数
静态绑定,他们是类对象直接可调用的,而不需要任何查表操作,因此调用的速度也快于虚函数。
C++面向对象总结——虚指针与虚函数表的更多相关文章
- C++对象内存模型2 (虚函数,虚指针,虚函数表)
从例子入手,考察如下带有虚函数的类的对象内存模型: class A { public: virtual void vfunc1(); virtual void vfunc2(); void func1 ...
- C++对象内存模型2 (虚函数,虚指针,虚函数表)(转)
class A { public: virtual void vfunc1(); virtual void vfunc2(); void func1(); void func2(); virtual ...
- C++虚函数和虚函数表
前导 在上面的博文中描述了基类中存在虚函数时,基类和派生类中虚函数表的结构. 在派生类也定义了虚函数时,函数表又是怎样的结构呢? 先看下面的示例代码: #include <iostream> ...
- 深入理解类成员函数的调用规则(理解成员函数的内存为什么不会反映在sizeof运算符上、类的静态绑定与动态绑定、虚函数表)
本文转载自:http://blog.51cto.com/9291927/2148695 总结: 一.成员函数的内存为什么不会反映在sizeof运算符上? 成员函数可以被看作是类 ...
- C++ 关于类与对象在虚函数表上唯一性问题 浅析
[摘要] 非常多教材上都有介绍到虚指针.虚函数与虚函数表.有的说类对象共享一个虚函数表,有的说,一个类对象拥有一个虚函数表.还有的说,不管用户声明了多少个类对象,可是,这个VTABLE虚函数表仅仅有一 ...
- 从零开始学C++之虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数
一.多态 多态性是面向对象程序设计的重要特征之一. 多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为. 多态的实现: 函数重载 运算符重载 模板 虚函数 (1).静态绑定与动态绑 ...
- 深入剖析C++多态、VPTR指针、虚函数表
在讲多态之前,我们先来说说关于多态的一个基石------类型兼容性原则. 一.背景知识 1.类型兼容性原则 类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代.通过公有继承,派 ...
- C++学习 - 虚表,虚函数,虚函数表指针学习笔记
http://blog.csdn.net/alps1992/article/details/45052403 虚函数 虚函数就是用virtual来修饰的函数.虚函数是实现C++多态的基础. 虚表 每个 ...
- C++虚函数表解析(图文并茂,非常清楚)( 任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法)good
C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有“多种形态”,这是一种泛型技术 ...
随机推荐
- 1、mysql基础入门(2)
1.4.常用非关系型数据库产品介绍: 1.Memcached(key-value)数据库:
- 使用Oracle SQL Developer报错:Unable to find a Java Virtual Machine
1.环境 win7 x64,oracle 11g r2,jdk6 x64 2.问题 第一次启动Oracle SQL Developer的时候会让我们填写java.exe的路径,我在jdk安装目录下的b ...
- OpenMVG 系列 (1):入门简介
1 OpenMVG 简介 全称 Open Multiple View Geometry,是法国人 Pierre Moulon 读博期间开源的一个 C++ 库 最早版本 OpenMVG 0.1 是 ...
- JavaWeb入门知识梳理
万维网 Web App(Web应用程序)是一种可以通过万维网访问的应用程序,用户只需要连接互联网和计算机安装浏览器,即可通过URI在线使用某个Web App,而不需要再安装客户端到计算机上.Web A ...
- Spring:Spring-IOC三种注入方式、注入不同数据类型
一.Spring IOC(依赖注入的三种方式): 1.Setter方法注入 package com.jpeony.spring.setter; import com.jpeony.spring.com ...
- Laravel + Swoole 打造IM简易聊天室
最近在学习Swoole,利用Swoole扩展让PHP生动了不少,本篇就来Swoole开发一款简易的IM聊天室 应用场景:实现简单的即时消息聊天室. (一)扩展安装 pecl install swool ...
- 『心善渊』Selenium3.0基础 — 23、Selenium元素等待
目录 1.什么是元素等待 2.为什么要设置元素等待 3.Selenium中常用的等待方式 4.强制等待 5.隐式等待 (1)隐式等待介绍 (2)示例 6.显式等待 (1)显式等待介绍 (2)语法 (3 ...
- Http2.0详解
前言 HTTP/1.1协议为现在网络提供了20年的支持.从那时起,网站已经从静态的.文本驱动的文档发展为交互式的.富媒体的应用程序.在此期间底层协议保持不变这一事实正好说明了它的通用性和能力.但随着网 ...
- Adaptive AUTOSAR 学习笔记 5 - 架构 - 物理视图
本系列学习笔记基于 AUTOSAR Adaptive Platform 官方文档 R20-11 版本 AUTOSAR_EXP_PlatformDesign.pdf 缩写 AP:AUTOSAR Adap ...
- 题解 guP4552 IncDec Sequence
这道题是一道差分的题目 差分数组p即p[i]=a[i]-a[i-1] 如果我们把一个区间[l,r]里的数+1,那么我们不难发现p[l]'=a[l]+1-a[l-1]=p[l]+1,p[r+1]'=a[ ...