多态的原理
1.虚表的概念
//这里呢给大家出一个笔试题目:求下面这个类的大小
class Base{
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};
int main()
{
cout<<sizeof(Base)<<endl;
return 0;
}
运行上面的这个程序我们会发现答案是8(如果是x64环境下的话才是16),那么这是为什么呢?为什么答案是8不是4呢?难道是函数?如果有同学认为是函数占用的内存,那就建议同学还是重新复习一下类,当然我也可以帮助各位仁兄们一起复习一下,因为类的内存是由成员变量和成员函数等来决定的,而在执行程序的时候成员变量与成员函数是分开进行计算的,也就是说成员函数占用的那部分空间并没有算作类中,那为什么这个类的内存大小不是4呢?
那我们来看一下这个类的成员有哪些。
在这里
在这里我们可以看到这种图片,中b的成员有哪些呢?有一个私有变量_b还有一个指针_vptr那么其实我们就可以猜测到这个_vptr就是我们多出来的四个字节的内存。
那么这个指针为什么会出现他的作用又是什么它指向的内容又是什么呢?我们接着往下分析。
这里给大家一个代码
class Base {
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};
class Baseson:public Base
{
virtual void Func1()
{
cout << "Baseson" << endl;
}
private:
int _a = 1;
};
int main()
{
Base b;
Baseson b1;
cout << sizeof(b1) << endl;
return 0;
}
这段代码我们加入了Baseson非常浅显易懂,它是Base的儿子继承了Base,那么它的里面也有func1函数并且还跟Base中的构成了函数重写,(对函数重写有些不理解的可以看一下我的上一篇文章),这里呢我们可以查看一下派生类对象b1中有哪些成员,我们可以发现除了继承父类的成员外它也拥有着一个指针,我们可以看一下面这张图片。
这张图片我们可以看到派生类对象中也有一个指针这个指针指向的是Baseson中的func地址,而基类对象中的指针指向的则是基类的func函数,那么我们可以得到什么信息呢?
1.首先就是这个指针是一个函数指针
2.其次有虚拟函数的类才会有这个指针,并且,这个一个有虚拟函数的类肯定会有这个指针,不是说一个对象一个指针而是一个类拥有一个指针。
3.基类b对象和派生类b1对象虚表是不一样的,这里我们发现Func1完成了重写,所以b1的虚表中存的是重写的Basesona::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
4.通过上面的总结我们应该也已经猜测到了一些那就是其实虚表没有那么复杂他就是一个函数数组而已
5.那么我们可以推测一下为什么说在原理层次重写可以说是覆盖呢?其实这我们从派生类虚函数表的生成步骤就可以名表首先派生类会先将基类的虚表拷贝一份下来,之后再进行检查一下查看哪些函数构成了函数重写,如果构成了函数重写就将构成函数重写的派生类函数的地址进行覆盖,所以从原理层次来看这个属于覆盖。
6. 这里还有一个童鞋们很容易混淆的问题:虚函数存在哪的?虚表存在哪的?
错误回答:虚函数存在虚表中,虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。
注意
虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的
呢?实际我们去验证一下会发现vs下是存在代码段的,Linux g++下大家自己去验证
2.究竟什么是多态
上面分析了这么1700多字那这多态的原理到底是什么呢?大家不要着急我给大家看一下下面这个代码
#include<iostream>
using namespace std;
class person {
public:
virtual void buyTicket()
{
cout << "买票全价" << endl;
}
private:
};
class student:public person{
public:
virtual void buyTicket()
{
cout << "买票半价" << endl;
}
private:
};
void func(person& p)
{
p.func();
}
int main()
{
person per;
student pe;
func(per);
func(pe);
return 0;
}
这个代码的运行结果呢就是传入person的对象打印全价,而student则打印半价那这是为什么呢?这是一个很基础的多态它的原理其实很简单我给大家剖析一下。
其实就是传入的对象不同使用的虚表就不同而不同的虚表对应的是不同的buyticket函数这里的原理其实就是调用了不同的指针。那么它的指向是怎么完成的呢?
大家可以看一下上面这张图片这是我在知乎上找到的。我认为画的是很好的。
这里呢有三个类A,B,C。其中呢,他们各自有一个虚表其各自虚表中指向的内容分别是什么呢?
类A包括两个虚函数,故A vtbl包含两个指针,分别指向A::vfunc1()和A::vfunc2()。
类B继承于类A,故类B可以调用类A的函数,但由于类B重写了B::vfunc1()函数,故B vtbl的两个指针分别指向B::vfunc1()和A::vfunc2()。
类C继承于类B,故类C可以调用类B的函数,但由于类C重写了C::vfunc2()函数,故C vtbl的两个指针分别指向B::vfunc1()(指向继承的最近的一个类的函数)和C::vfunc2()。
有了上面我给大家总结的派生类虚表是如何构建的相信大家对这个图应该就会理解的非常轻松了。
结语
那么关于虚表我就分享到这里,希望各位认为写的不错的可以给个点赞关注来一波,就当是支持一下了若有不足或差错也可私信补充改正。