C++ 虚函数表详解【建议收藏】
🎈 作者:Linux猿
🎈 简介:CSDN博客专家🏆,华为云享专家🏆,C/C++、面试、刷题、算法尽管咨询我,关注我,有问题私聊!
🎈 欢迎小伙伴们点赞👍、收藏⭐、留言💬
这篇文章主要对虚函数表进行分析总结。虚函数已经在先前的文章中进行了总结,这里就不再赘述:虚函数。
这里先额外介绍一下C++类的存储方式,然后介绍虚函数。
C++程序的内存格局通常分为五个区:全局数据区(data area),代码区(code area)、栈区(stack area)、堆区(heap area)(即自由存储区),文字常量区。全局数据区存放全局变量和静态变量,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后由系统释放。;所有类成员函数和非成员函数代码存放在代码区;为运行函数而分配的局部变量、函数参数、返回数据、返回地址等存放在栈区;文字常量区存储常量字符串,程序结束后由系统释放,余下的空间都被称为堆区。类的存储方式如下图所示:
其中对象数据中存储非静态成员变量、虚函数表指针以及虚基类表指针(如果继承多个)。这里就有一个问题,既然对象里不存储类的成员函数的指针,那类的对象是怎么调用公用函数代码的呢?对象对公用函数代码的调用是在编译阶段就已经决定了的,例如有类对象a,成员函数为show(),如果有代码a.show(),那么在编译阶段会解释为 类名::show(&a)。会给show()传一个对象的指针,即this指针。
从上面的this指针可以说明一个问题:静态成员函数和非静态成员函数都是在类的定义时放在内存的代码区的,但是类为什么只能直接调用静态成员函数,而非静态成员函数(即使函数没有参数)只有类对象能够调用的问题?原因是类的非静态成员函数其实都内含了一个指向类对象的指针型参数(即this指针),因而只有类对象才能调用(此时this指针有实值)。
虚函数表
1 内存布局
先说一下虚函数表的内存布局:
每一个有虚函数的类都有一个虚函数表,虚函数是整个类所共有的,虚函数表存储在对象内存最开始的位置。如果子类继承了多个父类,并且父类有虚函数,则子类要存储多个虚函数指针。如上图所示,如果继承了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的内存布局(类查看内存布局的方法):
运行结果:
Base类的虚函数表如下所示:
这里的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内存布局:
输出结果:
Node类的虚函数表如下所示:
如图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内存布局:
类Base内存布局:
输出结果同上一个。
Node类的虚函数表如下所示:
如图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内存布局:
输出结果:
Node类的虚函数表如下所示:
如图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内存布局:
输出结果为:
Node类的虚函数表如下所示:
如图 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
🎈 有任何疑问欢迎交流!
🎈 欢迎小伙伴们点赞👍、收藏⭐、留言💬
- 点赞
- 收藏
- 关注作者
评论(0)