一.虚函数的定义

被virtual关键字修饰的成员函数,目的是为了实现多态

ps:

关于多态【接口和实现分离,父类指针指向子类的实例,然后通过父类指针调用子类的成员函数,这样可以让父类指针拥有多种形态,所以称之为多态】

二.虚函数表

该表为一个类的虚函数的地址表,用于解决继承和覆盖的问题

1.拥有虚函数的类才有虚函数表

2.虚函数表属于类,然后类的所有对象通过虚函数表指针共享类的虚函数表

3.虚函数表的作用:当使用父类指针来操作子类对象时,虚函数表就像一个地图一样,指明了实际所应该调用的函数

4.c++编译器保证虚函数表的指针存在于对象实例中最前面的位置(为了保证在多层继承或者多重继承的情况下获得函数表的性能),这意味着我们可以通过对象实例的地址得到虚函数表,然后就可以遍历其中的虚函数指针,并且调用响应的虚函数

ps:多重继承:多个父类,多层继承:父类还存在父类

【通过虚函数表,遍历虚函数指针,调用响应的虚函数】

#include<bits/stdc++.h>
using namespace std;
class Base
{
public:
virtual void f()
{
cout << "Base::f" << endl;
}
virtual void g()
{
cout << "Base::g" << endl;
}
virtual void h()
{
cout << "Base::h" << endl;
} };
typedef void(*Fun)(void);
Base b;
Fun pFun = NULL; int main()
{
cout << "虚函数表地址:" << (int*)(&b) << endl;
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl; //通过虚函数表调用虚函数
pFun = (Fun)*((int*)*(int*)(&b)); // Base::f()
pFun();
pFun =(Fun)*((int*)*(int*)(&b)+); // Base::g()
pFun();
pFun =(Fun)*((int*)*(int*)(&b)+); // Base::h()
pFun();
}

结果:

虚函数表地址:0x477008
虚函数表 — 第一个函数地址:0x473668
Base::f
Base::g
Base::h

以上为无继承情况

1.单层继承无虚函数覆盖的情况

1)虚函数按照声明顺序放入表中

2)父类虚函数在前,子类虚函数在后

3)末尾点号为虚函数表的结尾标识符,在不同编译器下值不同

#include<bits/stdc++.h>
using namespace std;
class Base
{
public:
virtual void f()
{
cout << "Base::f" << endl;
}
virtual void g()
{
cout << "Base::g" << endl;
}
virtual void h()
{
cout << "Base::h" << endl;
} };
class Base_son:public Base
{
public:
virtual void f1()
{
cout << "Base_son::f1" << endl;
}
virtual void g1()
{
cout << "Base_son::g1" << endl;
}
virtual void h1()
{
cout << "Base_son::h1" << endl;
} }; typedef void(*Fun)(void);
Base_son d; Fun pFun = NULL; int main()
{
cout << "虚函数表地址:" << (int*)(&d) << endl;
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&d) << endl; //通过虚函数表调用虚函数
pFun = (Fun)*((int*)*(int*)(&d)); // Base::f()
pFun();
pFun =(Fun)*((int*)*(int*)(&d)+); // Base::g()
pFun();
pFun =(Fun)*((int*)*(int*)(&d)+); // Base::h()
pFun(); pFun =(Fun)*((int*)*(int*)(&d)+); // Base_son::f1()
pFun();
pFun =(Fun)*((int*)*(int*)(&d)+); // Base_son::g1()
pFun();
pFun =(Fun)*((int*)*(int*)(&d)+); // Base_son::h1()
pFun(); return ;
}

结果:

虚函数表地址:0x477008
虚函数表 — 第一个函数地址:0x473668
Base::f
Base::g
Base::h
Base_son::f1
Base_son::g1
Base_son::h1

2.单层继承有虚函数覆盖的情况

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

2)没有被覆盖的函数没有变化

#include<bits/stdc++.h>
using namespace std;
class Base
{
public:
virtual void f()
{
cout << "Base::f" << endl;
}
virtual void g()
{
cout << "Base::g" << endl;
}
virtual void h()
{
cout << "Base::h" << endl;
} };
class Base_son:public Base
{
public:
virtual void f()
{
cout << "Base_son::f" << endl;
}
virtual void g1()
{
cout << "Base_son::g1" << endl;
}
virtual void h1()
{
cout << "Base_son::h1" << endl;
} }; typedef void(*Fun)(void);
Base_son d; Fun pFun = NULL; int main()
{
cout << "虚函数表地址:" << (int*)(&d) << endl;
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&d) << endl; //通过虚函数表调用虚函数
pFun = (Fun)*((int*)*(int*)(&d)); // Base_son::f()
pFun();
pFun =(Fun)*((int*)*(int*)(&d)+); // Base::g()
pFun();
pFun =(Fun)*((int*)*(int*)(&d)+); // Base::h()
pFun(); pFun =(Fun)*((int*)*(int*)(&d)+); // Base_son::g1()
pFun();
pFun =(Fun)*((int*)*(int*)(&d)+); // Base_son::h1()
pFun(); return ;
}

结果:

虚函数表地址:0x477008
虚函数表 — 第一个函数地址:0x473650
Base_son::f
Base::g
Base::h
Base_son::g1
Base_son::h1

通过父类指针指向子类实例,子类覆盖父类方法,然后调用子类的方法,这样就实现了多态

Base *b=new Base_son(); b->f();

3.多重继承无虚函数覆盖

1)每个父类都有自己的虚函数表

2)子类的虚函数被放到第一个父类的虚函数表中

这样做是为了解决不同的父类类型指针指向同一个子类实例,而能够调用到实际的函数

4.多重继承存在虚函数覆盖

1)父类虚函数表中被覆盖的虚函数全部被替换成了子类的覆盖虚函数

这样我们就通过父类指向子类从而访问子类的f()了

Derive d;
Base1 *b1 = &d;
Base2 *b2 = &d;
Base3 *b3 = &d;
b1->f(); //Derive::f()
b2->f(); //Derive::f()
b3->f(); //Derive::f() b1->g(); //Base1::g()
b2->g(); //Base2::g()
b3->g(); //Base3::g()

使用虚函数表可以做一些违反c++语义的事情:

1)通过父类指针访问子类自己的虚函数

子类的虚函数X在父类中没有,所以子类的虚函数X没有覆盖父类的虚函数,但是如果我们通过父类的指针来访问子类自己的虚函数的编译器会报错

Base1 *b1 = new Derive();
b1->f1(); //编译出错

但是我们通过虚函数表可以做到这种违背C++语义的事情:使用父类指针访问子类自己的虚函数

2)访问父类non-public的虚函数

如果父类的虚函数是private或protected的,但是这些feipublic的父类虚函数同样会存在于虚函数表中,所以我们可以通过访问虚函数表访问到这些虚函数

附上多重继承有虚函数覆盖的样例代码:

#include <iostream>
using namespace std; class Base1 {
public:
virtual void f() { cout << "Base1::f" << endl; }
virtual void g() { cout << "Base1::g" << endl; }
virtual void h() { cout << "Base1::h" << endl; } }; class Base2 {
public:
virtual void f() { cout << "Base2::f" << endl; }
virtual void g() { cout << "Base2::g" << endl; }
virtual void h() { cout << "Base2::h" << endl; }
}; class Base3 {
public:
virtual void f() { cout << "Base3::f" << endl; }
virtual void g() { cout << "Base3::g" << endl; }
virtual void h() { cout << "Base3::h" << endl; }
}; class Derive : public Base1, public Base2, public Base3 {
public:
virtual void f() { cout << "Derive::f" << endl; }
virtual void g1() { cout << "Derive::g1" << endl; }
}; typedef void(*Fun)(void); int main()
{
Fun pFun = NULL; Derive d;
int** pVtab = (int**)&d; //Base1's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);
pFun = (Fun)pVtab[][];
pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);
pFun = (Fun)pVtab[][];
pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);
pFun = (Fun)pVtab[][];
pFun(); //Derive's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);
pFun = (Fun)pVtab[][];
pFun(); //The tail of the vtable
pFun = (Fun)pVtab[][];
cout<<pFun<<endl; //Base2's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
pFun = (Fun)pVtab[][];
pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
pFun = (Fun)pVtab[][];
pFun(); pFun = (Fun)pVtab[][];
pFun(); //The tail of the vtable
pFun = (Fun)pVtab[][];
cout<<pFun<<endl; //Base3's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
pFun = (Fun)pVtab[][];
pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
pFun = (Fun)pVtab[][];
pFun(); pFun = (Fun)pVtab[][];
pFun(); //The tail of the vtable
pFun = (Fun)pVtab[][];
cout<<pFun<<endl; return ;
}

关于虚函数和普通函数:

1.类中的虚函数是动态生成的,由虚函数表的指向进行访问,不为类的对象分配内存,没有虚函数表,就无法访问虚函数

2.类中的普通函数静态生成,不为类的对象分配内存也可访问

参考:左耳朵耗子:C++虚函数表解析

 

C++中的虚函数以及虚函数表的更多相关文章

  1. C++虚函数和虚函数表

    前导 在上面的博文中描述了基类中存在虚函数时,基类和派生类中虚函数表的结构. 在派生类也定义了虚函数时,函数表又是怎样的结构呢? 先看下面的示例代码: #include <iostream> ...

  2. C++虚函数及虚函数表解析

    一.背景知识(一些基本概念) 虚函数(Virtual Function):在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数.纯虚函数(Pure Virtual Functio ...

  3. C++ 由虚基类 虚继承 虚函数 到 虚函数表

    //虚基类:一个类可以在一个类族中既被用作虚基类,也被用作非虚基类. class Base1{ public: Base1(){cout<<"Construct Base1!&q ...

  4. 20140321 sizeof 虚函数与虚函数表 静态数组空间 动态数组空间 位字段

    1.静态的数组空间char a[10];sizeof 不能用于1:函数类型 2:动态的数组空间new3:位字段 函数类型:int fun();sizeof(fun())计算的是返回类型的大小,并不是函 ...

  5. c++ 虚函数多态、纯虚函数、虚函数表指针、虚基类表指针详解

    静态多态.动态多态 静态多态:程序在编译阶段就可以确定调用哪个函数.这种情况叫做静态多态.比如重载,编译器根据传递给函数的参数和函数名决定具体要使用哪一个函数.动态多态:在运行期间才可以确定最终调用的 ...

  6. C++中的纯虚函数和虚函数的作用

    1. 虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class) ...

  7. 为何JAVA虚函数(虚方法)会造成父类可以"访问"子类的假象?

      首先,来看一个简单的JAVA类,Base. 1 public class Base { 2 String str = "Base string"; 3 protected vo ...

  8. C++基础知识 基类指针、虚函数、多态性、纯虚函数、虚析构

    一.基类指针.派生类指针 父类指针可以new一个子类对象 二.虚函数 有没有一个解决方法,使我们只定义一个对象指针,就可以调用父类,以及各个子类的同名函数? 有解决方案,这个对象指针必须是一个父类类型 ...

  9. virtual之虚函数,虚继承

    当类中包含虚函数时,则该类每个对象中在内存分配中除去数据外还包含了一个虚函数表指针(vfptr),指向虚函数表(vftable),虚函数表中存放了该类包含的虚函数的地址. 当子类通过虚继承的方式从父类 ...

  10. C++纯虚函数、虚函数、实函数、抽象类,重载、重写、重定义

    首先,面向对象程序设计(object-oriented programming)的核心思想是数据抽象.继承.动态绑定.通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不 ...

随机推荐

  1. c++ 字符串相加

    1. append string a= "xxx"; string b="yyy"; a.append(b); 结果 a = “xxxyyy”;

  2. 2019.12.06 java基础

    JRE:运行环境(包含JVM(Java Virtual Machine)- Java虚拟机和核心类库) JDK: JAVA语言的软件开发工具包(Java Development Kit) Dos命令行 ...

  3. jsDOM分享1

    java scrip-DOM概念分享 在java script中有三大核心分别为:javascript语法,DOM,BOM. 今天分享一下在学习dom后的一些理解,希望大家支持. 绑定事件 之前学习过 ...

  4. pgloader 学习(一)支持的特性

    pgloader 是一个不错的多种格式数据同步到pg 的工具,pgloader 使用postrgresql 的copy 协议进行高效的数据同步处理 特性 加载文件到内容pg 多种数据源格式的支持 cs ...

  5. 使用RedisDesktopManager客户端无法连接Redis服务器问题解决办法

    是否遇到安装完成后连不上的问题? 那么这篇教程能解决. 执行步骤: 1.修改redis文件夹下redis.cong文件,在bind 127.0.0.1行前面加#注释掉这一行,使能远程连接(默认只能使用 ...

  6. DML 语言

    数据操纵语言(Data Manipulation Language, DML)是SQL语言中,负责对数据库对象运行数据访问工作的指令集. 以INSERT.UPDATE.DELETE三种指令为核心,分别 ...

  7. Redis 下载 安装

    Redis 官网 https://redis.io/ github 主页 https://github.com/antirez/redis 下载页面 https://redis.io/download ...

  8. 解决Bootstrap标签页(Tab)插件切换echarts不显示问题

    1.参考连接:https://blog.csdn.net/qq_24313955/article/details/78363981 问题描述:在echarts跟bootstrap选项卡整合的时候,默认 ...

  9. NGINX Cache Management (.imh nginx)

    In this article, we will explore the various NGINX cache configuration options, and tips on tweaking ...

  10. Weblogic部署web项目获取项目根目录为null

    写在前面 图片上传功能, web项目部署在本地Tomcat上并没有问题, 但是打成war包部署到Linux服务器Weblogic下却出现如题问题, 导致图片上传失败. 问题代码 String real ...