在C++中,多态:见名知意,就是多种形态.虚函数的主要作用就是实现多态,就是父类的指针/引用调用重写的虚函数,当父类的指针/引用指向父类对象时调用的是父类的虚函数,指向子类时指向子类的虚函数.
多态的实现原理
在有虚函数的对象实例中都存在一张虚函数表(简称虚表),而虚表中存放的则是实际应该调用的虚函数.
下面我们通过代码来模拟查找一下:
class AA
{
public:
virtual void Show()
{
cout<<"Show()"<virtual void Display()
{
cout<<"Display()"<private:
int _a;
};
在VS的环境中,在一个类中写两个虚函数,是怎么存储的呢?
通过求类的大小和从监视窗口中可以看到,类的大小指的是成员变量的大小而不包含成员函数.在存储虚函数的时候是存一个虚函数指针,然后将每个虚函数的地址,每个地址指向对应的虚函数.所以在该类中虚函数的大小为8,是因为类中有一个成员变量和一个指向虚函数表的指针组成的.
单继承与虚表
class AA
{
public:
virtual void Show()
{
cout<<"Show()"<virtual void Display()
{
cout<<"Display()"<private:
int _a;
};
class BB:public AA
{
public:
virtual void Fun()
{
cout<<"Fun()"<private:
int _b;
};
BB类继承了AA类,并且有自己的虚函数.
同样的从监事窗口查看类的大小和虚函数的存储,类的大小为12(因为继承了AA类的同时,还有一个BB类自己的成员变量以及一个虚函数表指针),可是虚函数表中却没有BB类中自己的独有的虚函数???其实呢,这儿就是VS的一个BUG所在,虚函数Fun()就在Display()虚函数的下面.
遇到上面的情况呢,我们可以通过打印虚函数表来查看:
typedef void (*show)();
void ShowVtrPtr(int** table)
{
cout<<"打印虚表:"<int i = 0;
for(;table[i] != NULL;++i)
{
cout<<" "<" "<" ";
show s = (show)table[i];
s();
}
cout<
同时打印上面两个类AA与BB.打印结果如下图所示.
我们发现,如果类中有虚函数,就一定至少有一个有一个指向所有虚函数的指针.简单的理解一下,当子类继承父类时,也会将对应的虚函数继承下来,继承虚函数的过程为:
- 先开辟空间.
- 拷贝父类的虚表.
3.如果在子类中重写了父类中的函数,此时就会在集成后将父类的虚表覆盖并将其改为子类的虚表.
可以发现,当类定义好以后,就不会再随意去修改类,所以虚表一般会存在于内存中的常量区或者静态区.
多继承与虚表
当一个类有两个父类或多个父类时,又会发生什么故事呢?
class AA
{
public:
virtual void Show()
{
cout<<"AA::Show()"<virtual void Display()
{
cout<<"AA::Display()"<private:
int _a;
};
class BB
{
public:
virtual void Show()
{
cout<<"BB::Show()"<virtual void Display()
{
cout<<"BB::Display()"<virtual void BB1()
{
cout<<"BB::BB1()"<private:
int _b;
};
class CC:public AA,public BB
{
private:
int _c;
};
CC类同时继承了AA类和BB类.

CC c;
cout<<sizeof(c)<
此时,sizeof(c)的大小为20,却不是16,说明此时CC类在继承了AA类和BB类时,不止生成了一个虚表指针,而是有两个虚表指针.
可以通过打印虚表指针来查看:
ShowVtrTable((int**)&c));
ShowVtrTable((int**)((char*)&c + sizeof(a))));
即在内存中是这样存储的:(先继承的在前面,所以如果CC类有自己的虚函数时,也会将虚函数表存于先继承的那个表里(在本类中即存于AA类的虚表中))

菱形继承与虚表
我们都知道:菱形继承会存在两个问题:①数据冗余–>造成浪费内存.②二义性(即不确定性).解决:->虚继承(会产生自己的虚基表).
那么,当菱形继承中遇到了虚函数,遇到了虚表那又会发生什么样的故事呢?
class AA
{
public:
virtual void Show()
{
cout<<"AA::Show()"<virtual void Display()
{
cout<<"AA::Display()"<int _a;
};
class BB:virtual public AA
{
public:
virtual void Show()
{
cout<<"BB::Show()"<virtual void Display()
{
cout<<"BB::Display()"<virtual void BB1()
{
cout<<"BB::BB1()"<int _b;
};
class CC:virtual public AA
{
public:
virtual void Show()
{
cout<<"CC::Show()"<virtual void CC1()
{
cout<<"CC::CC1()"<int _c;
};
class DD:public BB,public CC
{
public:
virtual void Show()
{
cout<<"DD::Show()"<int _d;
};
BB和CC分别虚继承了AA类.然后DD类同时继承了BB类和CC类.
此时,DD类的大小为:36.我们可以通过查看内存:
DD d;
d.BB::_a = 1;
d.CC::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
cout<<sizeof(d)<<endl;
总结:所以加起来DD类的大小就是36,并且在DD类中有3个虚表,分别是AA类,BB类和CC类的虚表,同时,DD类的虚表指针从与BB类的虚表指针指向的是同一个地方,但是它们只是指针相同,含义是不同的.
那么,哪个是虚表指针,而哪个又是虚函数表指针呢?很简单啊,我们可以再打开一个内存的监事窗口来查看.
总结:在VS的环境下,先存的是虚函数表指针,再存储的是虚基表指针.
然后我们可以打印出这个例子中的3个虚函数表:
ShowVtrTable((int**)(*(int**)&d));
ShowVtrTable((int**)(*(int**)((char*)&d + sizeof(b) - sizeof(a))));
ShowVtrTable((int**)(*(int**)((char*)&d + sizeof(d) - sizeof(a))));
system("pause");

多态(与对象有关)
即多种状态.在C++ 中,多态分为静态多态和动态多态.
- 静态多态:即重载.由于是在编译期间确定的,所以就是静态多态.
- 动态多态:通过继承重写基类
构成多态的条件:
- 父类的指针或引用.
- 调用的函数必须是虚函数的重写.
写写一个简单的多态来观察:
class AA
{
public:
virtual void Show()
{
cout<<"AA::Show()"<private:
int _a;
};
class BB:virtual public AA
{
public:
virtual void Show()
{
cout<<"BB::Show()"<private:
int _b;
};
class CC:virtual public AA
{
public:
virtual void Show()
{
cout<<"CC::Show()"<private:
int _c;
};
class DD:public BB,public CC
{
public:
virtual void Show()
{
cout<<"DD::Show()"<private:
int _d;
};
void f(AA& A)
{
A.Show();
}
可以通过不同的对象来调用同一个函数.
AA a;
BB b;
CC c;
DD d;
f(a);
f(b);
f(c);
f(d);
那么,为什么可以这么使用呢?我们可以通过反汇编来查看它们.

总结:静态多态在编译期间就可以找到函数对应的地址,从而去掉用它.
动态多态是在运行期间才去找调用的函数.
