前言
本次笔记记录如下知识点
- 虚函数表指针位置分析
- 继承关系下虚函数的调用
- 虚函数表的分析
一、虚函数表指针位置分析
- 一个类中如果有虚函数,针对这个类会产生一个虚函数表,生成这个类对象的时候,该对象就有一个指针用来指向这个虚函数表的起始位置。
- 虚函数表指针位于对象内存的起始位置。
如下示例:
#include <iostream>
using namespace std;
class A{
public:
int i;
virtual void fun(){ cout<<"虚函数执行";};
};
int main()
{
A a;
int len = sizeof(a);
cout<< "虚函数指针的地址:"<< (int*)&a <<endl;;
/*
reinterpret_cast 用于进行各种不同类型的指针之间、不同
类型的引用之间以及指针和能容纳指针的整数类型之间的转换。
*/
char *p1 = reinterpret_cast<char*>(&a);
char *p2 = reinterpret_cast<char*>(&a.i);
//判断&a.i的地址是否为&a,如果不是,说明虚函数指针在内存起始开头
if(p1 == p2)
{
cout<<"虚函数表指针位于对象内存的末尾";
}
else
{
cout<<"虚函数表指针位于对象内存的开头";
}
return 0;
}
结果如下:
二、继承关系下虚函数的调用
- 父类对象调用的虚函数都是父类的。
- 子类和父类如果虚函数相同,子类会调用子类的虚函数。
如下示例:
#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;}
};
class Derive:public Base{
public:
void g() {cout<<"Derive"<<endl;;}
};
//定义函数指针
typedef void (*Func)(void);
int main()
{
cout << sizeof(Base) <<endl;
cout << sizeof(Derive) <<endl;
Derive* d = new Derive();
long* pvptr = (long*)d;
//将对象d转换成虚函数表指针vptr
long* vptr = (long*)(*pvptr);
//打印虚函数地址
for(int i = 0;i<=4;i++)
{
cout<<"vptr"<<i<<";0x:"<<vptr[i]<<endl;
}
Func f = (Func)vptr[0];
Func g = (Func)vptr[1];
Func h = (Func)vptr[2];
Func i = (Func)vptr[3];
Func j = (Func)vptr[4];
//打印虚函数 i() j()两个运行异常
f();
g();
h();
//创建父类对象,调用虚函数
Base* dpar = new Base();
long* pvptrpar = (long*)dpar;
long* vptrpar = (long*)(*pvptrpar);
//打印虚函数地址
for(int i = 0;i<=4;i++)
{
cout<<"vptr"<<i<<";0x:"<<vptrpar[i]<<endl;
}
Func fpar = (Func)vptrpar[0];
Func gpar = (Func)vptrpar[1];
Func hpar = (Func)vptrpar[2];
Func ipar = (Func)vptrpar[3];
Func jpar = (Func)vptrpar[4];
//打印虚函数 ipar() jpar()两个运行异常
fpar();
gpar();
hpar();
return 0;
}
运行结果如下:
三、虚函数表的分析
分析父类对象和子类对象内存布局
通过上图我们分析如下几个知识点
1、包含虚函数的类才会有虚函数表,同属于一个类的对象共享这个虚函数表,但是每个对象都有各自的vptr(虚函数指针),指针指向的地址是相同的。
2、父类中有虚函数等于子类中有虚函数。
3、如果子类完全没有新的虚函数,则可以认为子类的虚函数表和父类的虚函数表内容一致。
4、超出虚函数表部分的内存不可知也不可预测。
多重继承虚函数表分析
代码如下
class Base1
{
public:
virtual void f() {cout<<"Base1::f()"<<endl;}
virtual void g() {cout<<"Base1::g()"<<endl;}
};
class Base2
{
public:
virtual void h() {cout<<"Base2::h()"<<endl;}
virtual void i() {cout<<"Base2::i()"<<endl;}
};
class Derived:public Base1,public Base2
{
virtual void f() {cout<<"Derived::f()"<<endl;}
virtual void i() {cout<<"Derived::i()"<<endl;}
virtual void mh() {cout<<"Derived::mh()"<<endl;}
virtual void mi() {cout<<"Derived::mi()"<<endl;}
virtual void mj() {cout<<"Derived::mj()"<<endl;}
};
int main()
{
cout<<sizeof(Base1)<<endl; //4
cout<<sizeof(Base2)<<endl; //4
cout<<sizeof(Derived)<<endl;//8
Derived ins;
return 0;
}
代码结构如下如所示
通过上图我们分析如下几个知识点
1、子类对象ins有两个虚函数指针--------vptr1和vptr2
2、类Derived有两个虚函数表,因为它继承自两个基类
3、子类和第一个基类共用一个vptr
4、子类中的虚函数覆盖了父类中的同名虚函数。