C++ 虚函数表详解【建议收藏】

举报
Linux猿 发表于 2021/09/30 16:25:13 2021/09/30
【摘要】 C++ 虚函数表详解

🎈 作者:Linux猿

🎈 简介:CSDN博客专家🏆,华为云享专家🏆,C/C++、面试、刷题、算法尽管咨询我,关注我,有问题私聊!

🎈 欢迎小伙伴们点赞👍、收藏⭐、留言💬


这篇文章主要对虚函数表进行分析总结。虚函数已经在先前的文章中进行了总结,这里就不再赘述:虚函数。

这里先额外介绍一下C++类的存储方式,然后介绍虚函数。

    C++程序的内存格局通常分为五个区:全局数据区(data area),代码区(code area)、栈区(stack area)、堆区(heap area)(即自由存储区),文字常量区。全局数据区存放全局变量和静态变量,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后由系统释放。;所有类成员函数和非成员函数代码存放在代码区;为运行函数而分配的局部变量、函数参数、返回数据、返回地址等存放在栈区;文字常量区存储常量字符串,程序结束后由系统释放,余下的空间都被称为堆区。类的存储方式如下图所示:

虚函数表.png

 其中对象数据中存储非静态成员变量、虚函数表指针以及虚基类表指针(如果继承多个)。这里就有一个问题,既然对象里不存储类的成员函数的指针,那类的对象是怎么调用公用函数代码的呢?对象对公用函数代码的调用是在编译阶段就已经决定了的,例如有类对象a,成员函数为show(),如果有代码a.show(),那么在编译阶段会解释为 类名::show(&a)。会给show()传一个对象的指针,即this指针。

    从上面的this指针可以说明一个问题:静态成员函数和非静态成员函数都是在类的定义时放在内存的代码区的,但是类为什么只能直接调用静态成员函数,而非静态成员函数(即使函数没有参数)只有类对象能够调用的问题?原因是类的非静态成员函数其实都内含了一个指向类对象的指针型参数(即this指针),因而只有类对象才能调用(此时this指针有实值)。

虚函数表

1 内存布局

    先说一下虚函数表的内存布局:

虚函数表1.png

 每一个有虚函数的类都有一个虚函数表,虚函数是整个类所共有的,虚函数表存储在对象内存最开始的位置。如果子类继承了多个父类,并且父类有虚函数,则子类要存储多个虚函数指针。如上图所示,如果继承了n个父类,并且每个父类都有虚函数,那子类会有n个虚函数表指针。

2 无继承

#include <iostream>
using namespace std;
 
typedef void (*Fun)(void);
class Base{
public:
    virtual void f(){
        cout<<"Base::f()"<<endl;
    }
    virtual void g(){
        cout<<"Base::g()"<<endl;
    }
    virtual void h(){
        cout<<"Base::h()"<<endl;
    }
    void dispaly(){
        cout<<"Base::display()"<<endl;
    }
private:
    int data;
};
int main(int argc, char *argv[])
{
    Base a;
    Fun pf = NULL, pg = NULL, ph = NULL;
    pf = (Fun)*((int*)*((int*)(&a)));
    pg = (Fun)*((int*)*((int*)(&a))+1);
    ph = (Fun)*((int*)*((int*)(&a))+2);
    pf();
    pg();
    ph();
    cout<<"sizeof(a) = "<<sizeof(a)<<endl;
    return 0;
}

类Base的内存布局(类查看内存布局的方法):

虚函数表2.png

运行结果:

虚函数表3.png

Base类的虚函数表如下所示:

虚函数表4.png

这里的sizeof(a) = 8 是虚函数表指针的大小 4 和 data的大小4。图3 最后一个虚函数表中的最后一个位置表示虚函数表的结束。

2 一般继承(无虚函数覆盖)

#include <iostream>
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;
    }
    void dispaly(){
        cout<<"Base::display()"<<endl;
    }
private:
    int data;
};
class Node : public Base{
public:
    virtual void f1(){
        cout<<"Node::f1()"<<endl;
    }
    virtual void g1(){
        cout<<"Node::g1()"<<endl;
    }
    virtual void h1(){
        cout<<"Node::h1()"<<endl;
    }
    void print(){
        cout<<"Node::print()"<<endl;
    }
private:
    int val;
};
int main()
{
    Node a;
    Base b;
    cout<<"sizeof(a) = "<<sizeof(a)<<endl;
    cout<<"sizeof(b) = "<<sizeof(b)<<endl;
    return 0;
}

类Node内存布局:

虚函数表5.png

输出结果:

虚函数表6.png

Node类的虚函数表如下所示:

虚函数表7.png

如图4 所以,虚函数表现存储父类的虚函数,然后存子类的虚函数。

3 一般继承(有虚函数覆盖)

#include <iostream>
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;
    }
    void dispaly(){
        cout<<"Base::display()"<<endl;
    }
private:
    int data;
};
class Node : public Base{
public:
    virtual void f(){//覆盖Base的f()
        cout<<"Node::f1()"<<endl;
    }
    virtual void g1(){
        cout<<"Node::g1()"<<endl;
    }
    virtual void h1(){
        cout<<"Node::h1()"<<endl;
    }
    void print(){
        cout<<"Node::print()"<<endl;
    }
private:
    int val;
};
int main()
{
    Node a;
    Base b;
    cout<<"sizeof(a) = "<<sizeof(a)<<endl;
    cout<<"sizeof(b) = "<<sizeof(b)<<endl;
    return 0;
}

类Node内存布局:

虚函数表8.png

类Base内存布局:

虚函数表9.png

输出结果同上一个。

Node类的虚函数表如下所示:

虚函数表10.png

如图5 所示,如果子类覆盖了父类的虚函数,则父类的虚函数会替换为子类的虚函数,没有被覆盖的虚函数依旧,这样当把子类的地址赋给父类指针的时候就可以实现多态了。

4 多重继承(无虚函数覆盖)

#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;
    }
    void dispaly(){
        cout<<"Base1::display()"<<endl;
    }
private:
    int data1;
};
class Base2{
public:
    virtual void f(){
        cout<<"Base2::f()"<<endl;
    }
    virtual void g(){
        cout<<"Base2::g()"<<endl;
    }
    virtual void h(){
        cout<<"Base2::h()"<<endl;
    }
    void dispaly(){
        cout<<"Base2::display()"<<endl;
    }
private:
    int data2;
};
class Base3{
public:
    virtual void f(){
        cout<<"Base3::f()"<<endl;
    }
    virtual void g(){
        cout<<"Base3::g()"<<endl;
    }
    virtual void h(){
        cout<<"Base3::h()"<<endl;
    }
    void dispaly(){
        cout<<"Base3::display()"<<endl;
    }
private:
    int data3;
};
class Node : public Base1, public Base2, public Base3{
public:
    virtual void f1(){
        cout<<"Node::f1()"<<endl;
    }
    virtual void g1(){
        cout<<"Node::g1()"<<endl;
    }
    virtual void h1(){
        cout<<"Node::h1()"<<endl;
    }
    void print(){
        cout<<"Node::print()"<<endl;
    }
private:
    int val;
};
int main()
{
    Base1 b1;
    Base2 b2;
    Base3 b3;
    Node a;
    cout<<"sizeof(b1) = "<<sizeof(b1)<<endl;
    cout<<"sizeof(b2) = "<<sizeof(b2)<<endl;
    cout<<"sizeof(b3) = "<<sizeof(b3)<<endl;
    cout<<"sizeof(a) = "<<sizeof(a)<<endl;
    return 0;
}

类Node内存布局:

虚函数表11.png

输出结果:

虚函数表12.png

Node类的虚函数表如下所示:

虚函数表13.png

如图6 所示,虚函数表的指针是按照声明的顺序来的,子类的虚函数放入第一个虚函数表里。

5 多重继承(有虚函数覆盖)

#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;
    }
    void dispaly(){
        cout<<"Base1::display()"<<endl;
    }
private:
    int data1;
};
class Base2{
public:
    virtual void f(){
        cout<<"Base2::f()"<<endl;
    }
    virtual void g(){
        cout<<"Base2::g()"<<endl;
    }
    virtual void h(){
        cout<<"Base2::h()"<<endl;
    }
    void dispaly(){
        cout<<"Base2::display()"<<endl;
    }
private:
    int data2;
};
class Base3{
public:
    virtual void f(){
        cout<<"Base3::f()"<<endl;
    }
    virtual void g(){
        cout<<"Base3::g()"<<endl;
    }
    virtual void h(){
        cout<<"Base3::h()"<<endl;
    }
    void dispaly(){
        cout<<"Base3::display()"<<endl;
    }
private:
    int data3;
};
class Node : public Base1, public Base2, public Base3{
public:
    virtual void f(){  //覆盖父类的f()
        cout<<"Node::f1()"<<endl;
    }
    virtual void g1(){
        cout<<"Node::g1()"<<endl;
    }
    virtual void h1(){
        cout<<"Node::h1()"<<endl;
    }
    void print(){
        cout<<"Node::print()"<<endl;
    }
private:
    int val;
};
int main()
{
    Base1 b1;
    Base2 b2;
    Base3 b3;
    Node a;
    cout<<"sizeof(a) = "<<sizeof(b1)<<endl;
    cout<<"sizeof(a) = "<<sizeof(b2)<<endl;
    cout<<"sizeof(a) = "<<sizeof(b3)<<endl;
    cout<<"sizeof(a) = "<<sizeof(a)<<endl;
    return 0;
}

类Node内存布局:

虚函数表14.png

输出结果为:

虚函数表15.png

Node类的虚函数表如下所示:

虚函数表16.png

如图 7所示,父类被覆盖的函数f(),都被替换为子类的虚函数f()。

参考文献:

[1] https://blog.csdn.net/fuzhongmin05/article/details/59112081

[2] https://www.zhihu.com/question/31265363

[3] https://blog.csdn.net/haoel/article/details/1948051


🎈 有任何疑问欢迎交流!

🎈 欢迎小伙伴们点赞👍、收藏⭐、留言💬


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。