一.虚函数的定义

被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. LightOJ - 1326 - Race(DP)

    链接: https://vjudge.net/problem/LightOJ-1326 题意: Disky and Sooma, two of the biggest mega minds of Ba ...

  2. JavaScript基础04——数组的创建及方法

    数组的概念及定义 数组的概念:         一组数据,数据的组和         哪些数据的组和,只要是数据(所有数据),就可以放在数组中 数组的意义:         可以同时操作多个数据 数组 ...

  3. CF547E Mike and Friends 后缀自动机+线段树合并

    裸题,敲完后没调就过了 ~ code: #include <bits/stdc++.h> using namespace std; #define ll long long #define ...

  4. Switch ……case语句

    Switch(变量){ case 1: 如果变量和1的值相同,执行该处代码 break; case 2: 如果变量和2的值相同,执行该处代码 break; case 3: 如果变量和3的值相同,执行该 ...

  5. REdis主从复制之repl_backlog

    目录 目录 1 1. 前言 1 2. 配置项 1 3. redisServer 2 4. feedReplicationBacklog-写repl_backlog 3 5. addReplyRepli ...

  6. MAKEFILE编写学习--1

    makefile是在编译中大型程序中使用的自动化编译工具make依赖的指令文件.这样可以使得程序的编译更加便捷快速. makefile的一般规则如下: target ... : prerequisit ...

  7. 【03NOIP普及组】栈(信息学奥赛一本通 1924)(洛谷 1044)

    #include<bits/stdc++.h> using namespace std; int n,ans,m,k,ans2; ],f[],d[][],num[][],tmp[],s[] ...

  8. vue中父级与子组件生命周期的先后顺序

    1.vue的生命周期 2.views/createrCustormer.vue为父级     <template>     <expressService />   </ ...

  9. hdu1237 简单计算器[STL 栈]

    目录 题目地址 题干 代码和解释 参考 题目地址 hdu1237 题干 代码和解释 解本题时使用了STL 栈,要记得使用#include<stack>. 解本题时使用了isdigit()函 ...

  10. 3ds Max学习日记(十一)——如何给模型上贴图

    参考链接:https://jingyan.baidu.com/article/e4511cf38a810b2b845eaf1f.html   之前一直都不知道怎么在3dsMax里给模型上材质和贴图,被 ...