C++中的虚函数以及虚函数表

一.虚函数的定义

被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)+1);  // Base::g()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&b)+2);  // 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)+1);  // Base::g()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+2);  // Base::h()
    pFun();

    pFun =(Fun)*((int*)*(int*)(&d)+3);  // Base_son::f1()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+4);  // Base_son::g1()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+5);  // Base_son::h1()
    pFun();

    return 0;
}

结果:

虚函数表地址: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)+1);  // Base::g()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+2);  // Base::h()
    pFun();

    pFun =(Fun)*((int*)*(int*)(&d)+3);  // Base_son::g1()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+4);  // Base_son::h1()
    pFun();

    return 0;
}

结果:

虚函数表地址: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[0][0];
            pFun();

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);
            pFun = (Fun)pVtab[0][1];
            pFun();

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);
            pFun = (Fun)pVtab[0][2];
            pFun();

            //Derive‘s vtable
            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);
            pFun = (Fun)pVtab[0][3];
            pFun();

            //The tail of the vtable
            pFun = (Fun)pVtab[0][4];
            cout<<pFun<<endl;

            //Base2‘s vtable
            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
            pFun = (Fun)pVtab[1][0];
            pFun();

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
            pFun = (Fun)pVtab[1][1];
            pFun();

            pFun = (Fun)pVtab[1][2];
            pFun();

            //The tail of the vtable
            pFun = (Fun)pVtab[1][3];
            cout<<pFun<<endl;

            //Base3‘s vtable
            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
            pFun = (Fun)pVtab[2][0];
            pFun();

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
            pFun = (Fun)pVtab[2][1];
            pFun();

            pFun = (Fun)pVtab[2][2];
            pFun();

            //The tail of the vtable
            pFun = (Fun)pVtab[2][3];
            cout<<pFun<<endl;

            return 0;
}

关于虚函数和普通函数:

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

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

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

 

原文地址:https://www.cnblogs.com/yinbiao/p/10987640.html

时间: 2025-01-02 11:22:04

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

C++虚函数和虚函数表

前导 在上面的博文中描述了基类中存在虚函数时,基类和派生类中虚函数表的结构. 在派生类也定义了虚函数时,函数表又是怎样的结构呢? 先看下面的示例代码: 1 #include <iostream> 2 3 using namespace std; 4 5 class A 6 { 7 public: 8 virtual void funcA(){ cout<<"A"<<endl; } 9 }; 10 11 class B 12 { 13 public:

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

一.背景知识(一些基本概念) 虚函数(Virtual Function):在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数.纯虚函数(Pure Virtual Function):基类中没有实现体的虚函数称为纯虚函数(有纯虚函数的基类称为虚基类).C++  “虚函数”的存在是为了实现面向对象中的“多态”,即父类类别的指针(或者引用)指向其子类的实例,然后通过父类的指针(或者引用)调用实际子类的成员函数.通过动态赋值,实现调用不同的子类的成员函数(动态绑定).正是因为这种

C++虚函数与虚函数表

多态性可分为两类:静态多态和动态多态.函数重载和运算符重载实现的多态属于静态多态,动态多态性是通过虚函数实现的. 每个含有虚函数的类有一张虚函数表(vtbl),表中每一项是一个虚函数的地址, 也就是说,虚函数表的每一项是一个虚函数的指针. 没有虚函数的C++类,是不会有虚函数表的. 两张图: 简单例子: 1 #include <iostream> 2 #include <windows.h> 3 4 using namespace std; 5 6 class base 7 { 8

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

1. 虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class). 2. 虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义. 3. 虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式

C++中的 虚函数 纯虚函数 虚基类(virtual)

前言:需要了解三者的区别,必须要掌握多态的三个必要条件: 继承 重载 父类指针指向子类对象. 虚函数 纯虚函数 虚基类三者区别 1.虚函数是用于多态中virtual修饰父类函数,确保父类指针调用子类对象时,运行子类函数的. 2.纯虚函数是用来定义接口的,也就是基类中定义一个纯虚函数,基类不用实现,让子类来实现. 3.虚基类是用来在多继承中,比如菱形继承中,如果两个父类继承自同一个类,就只实例化一个父类 ①虚函数第一个是没有使用多态(只用继承)的一般实现方式: class A { public:

C++ 虚函数和虚继承浅析

本文针对C++里的虚函数,虚继承表现和原理进行一些简单分析,有希望对大家学习C++有所帮助.下面都是以VC2008编译器对这两种机制内部实现为例. 虚函数 以下是百度百科对于虚函数的解释: 定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数[1] 语法:virtual 函数返回类型 函数名(参数表) { 函数体 } 用途:实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数 函数声明和定义和普通的类成员函数一样,只是在返回值之前加入了关键字"vir

C++之易混淆知识点四---虚函数与虚继承

C++面向对象中,虚函数与虚继承是两个完全不同的概念. 一.虚函数 C++程序中只要类中含有虚拟函数,编译程序都会为此类生成一个对应的虚拟函数跳转表(vtbl),该虚拟函数跳转表是一个又若干个虚拟函数体入口地址组成的一个线性表.派生类的虚拟函数跳转表的前半部分由父类的vtbl得出,但是里面的内容不一定相同,后半部分则对应着自己新定义的虚拟函数. class Employee { protected: char *Name; int Age; public: void changeAge(int

C++ 虚函数&amp;纯虚函数&amp;抽象类&amp;接口&amp;虚基类(转)

http://www.cnblogs.com/fly1988happy/archive/2012/09/25/2701237.html 1. 多态 在面向对象语言中,接口的多种不同实现方式即为多态.多态是指,用父类的指针指向子类的实例(对象),然后通过父类的指针调用实际子类的成员函数. 多态性就是允许将子类类型的指针赋值给父类类型的指针,多态是通过虚函数实现的. 多态可以让父类的指针有“多种形态”,这是一种泛型技术.(所谓泛型技术,就是试图使用不变的代码来实现可变的算法). 2. 虚函数 2.1

虚函数与虚析构函数原理

----------------siwuxie095 关于虚函数和虚析构函数的实现原理,因为涉及到 函数指针, 所以先介绍什么是函数指针 函数指针 如果通过一个指针指向对象,就称其为 对象指针,指针除了可以 指向对象之外,也可以指向函数,就称其为 函数指针 函数的本质,其实就是一段二进制的代码,它写在内存中, 可以通过指针来指向这段代码的开头,计算机就会从开头 一直往下执行,直到函数的结尾,并通过指令返回回来 如果有这么 5 个函数指针,它们所存储的就是 5 个函数的函数地址,当 使用时,如:使

虚函数/纯虚函数/抽象类/接口/虚基类

1.多态 在面向对象语言中,接口的多种不同实现方式即为多态.多态是指,用父类的指针指向子类的实例(对象),然后通过父类的指针调用实际子类的成员函数. 在Java中,没有指针,就直接用父类实例化子类对象 多态性就是允许将子类类型的指针赋值给父类类型的指针,多态是通过虚函数实现的,多态可以让父类的指针有“多种形态”,这是一种泛型技术. 所谓泛型技术,就是试图使用不变的代码来实现可变的算法 2.虚函数 在基类的类定义中,定义虚函数的一般形式: Virtual 函数返回值类型 虚函数名(形参表){ 函数