代码描述:定义一个Person类为基类,ChinesePer 类与EnglishPer类都继承于此基类。
class Person
{
public:
void speak() {
cout << "说人话" << endl;
}
private:
int m_type = 1 ;
};
class ChinesePer :public Person {
public:
void speak() {
cout << "说中国话chinese..." << endl;
}
};
class EnglishPer :public Person {
public:
void speak() {
cout << "说英国话english..." << endl;
}
};
先看看此时各个类占用的内存信息:
int main() {
int person_size = sizeof(Person);
int Chineseper_size = sizeof(ChinesePer);
int Englishper = sizeof(EnglishPer);
}
可以看到三个类的大小都为4个字节,占用的情况就是 类中的m_type变量。
将基类中的 speak() 函数加上virtual关键字 ,成为虚函数后再次查看内存字节大小:
可以看到多出了4个字节大小的空间(X86),三个类的大小都为 8。其实就是多出了一个指针的大小,4个字节 ,创建一个子类对象就可以明显看出来:
可以得出结论一:继承过来的成员变量 m_type(4个字节) + _vfptr(4个字节) = 8个字节。
继续探索虚函数表的原理, 创建以下子类对象并通过父类指针指向子类对象调用speak函数发生多态:
int main(){
ChinesePer chs;
ChinesePer chs2;
EnglishPer eng;
Person *ptr = &chs; //父类指针指向子类对象
Person *ptr1 = &chs2;
Person *ptr2 = ŋ
ptr->speak();
ptr1->speak();
ptr2->speak();
}
以上代码执行后会发生多态:
此时我们看看三个对象的内存分布:
用图片简要描述一下就是:
在这里可以先得到结论二: 同一个子类的所有对象共享一个虚函数表,指向虚函数表的指针_vptr是相同的。
在内存分布上查看一下 eng对象的虚函数指针地址情况:
可以看到该虚函数指针的地址上的值,存放的正是 该子类中的重写函数 speak()函数的地址。
如果把这个地址的值,修改为 Chineseper 类中的重写函数speak() 的地址值,会发生什么?
手动修改了eng对象中虚函数指针内的地址值,将改值本来是存放的是 EnglishPer 类中的speak() 函数的地址,现在更改为 ChinesePer 类中speak() 函数的地址。
单步走发生多态:
至此可以得出结论三:
当基类函数加了virtual 关键字后,虚函数的调用方法是间接调用:先查虚函数表的地址(也就是指向虚函数表的指针 _vptr),再查虚函数表中的虚函数指针。
不妨在基类继续添加两个虚函数
class Person
{
public:
virtual void speak() {
cout << "说人话" << endl;
}
virtual void eat() {
cout << "吃饭" << endl;
}
virtual void sleep() {
cout << "睡觉" << endl;
}
private:
int m_type =1 ;
};
子类只重写了speak函数,查看一下chs对象的虚函数指针地址存放的值:
的确存放的还是各个函数的地址,且是连续存放的,因此在进行查表调用虚函数的时候,也是每移动4个字节指向的就是一个函数的指针地址。
可以简要描述一下形式就很直观了:
总结:
1.增加了virtual 关键字的对象头部4个字节是一个指针,指向了虚函数表的地址(单继承情况下)。
2.同一个子类的所有对象共享一个虚函数表,指向虚函数表的指针_vptr是相同的。
3.当基类函数加了virtual 关键字后,虚函数的调用方法是间接调用:先查虚函数表的地址(也就是指向虚函数表的指针 _vptr),再查虚函数表中的虚函数指针。
到此这篇关于C++深度探索虚函数指针示例的文章就介绍到这了,更多相关C++虚函数内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!