多态,顾名思义,就是多种形态。总的来说就是一句话:使基类指针或引用可以访问子类对象。
介绍多态前,先说函数调用捆绑。
把函数体与函数调用相联系称为捆绑(binding)。当捆绑在程序运行之前(由编译器和连接器)完成时,这个叫早捆绑(early bingding)。在C语言中,捆绑的方式只有一种——早捆绑。而在运行时发生的捆绑,叫做动态捆绑(dynamic binding)或者运行时捆绑(runtime binding)。在c++中,通过在基类中声明函数时使用virtual关键字,告诉编译器使用晚捆绑。晚捆绑只对virtual函数起作用,而且只在使用含有virtual函数的基类的地址时发生。
为了实现多态,编译器对每个包含虚函数的类创建一个vtab(虚函数表)和一个vptr(指针),并在构造函数中初始化vptr指向vtab,并把声明的虚函数的地址放到vtab中。一般这个vtab的地址是在类对象地址的最前面,所以现在这个时候,this指针和vptr指针指向的地址应该一致——指向这个类对象的首地址。
当子类继承这个基类时,子类也会继承这个vptr,而子类的vptr也指向自己的这个vptr,其vptr的位置与基类中vptr的位置一样——在对象的最前面,此时不论在子类中该函数是否有virtual关键字,这个函数都是虚函数。如果在子类中又增加了virtul函数,则编译器会在从基类继承的、子类自己的vtab最下面插入这个新的virtual函数的地址。
因为是晚绑定,所以函数在编译的过程中,不会绑定其调用地址。在程序运行中,调用virtual的时候,如果是通过基类指针或者引用调用虚函数时,如果该基类指针或引用指向的是子类对象,则会查找基类指针指向的子类的vtab,从而取得正确的函数地址,然后调用该函数,这就实现了多态。
下面看代码:
#include <iostream>
using namespace std;
class Base
{
public:
virtual void show(){cout<<"base show"<<endl;}
};
class A : public Base
{
public:
void show(){cout<<"A show"<<endl;}
};
class B : public Base
{
public:
void show(){cout<<"B show"<<endl;}
};
void pt(Base& b)
{
b.show();
}
int main()
{
A a;
B b;
Base& base = b;
pt(base);
return 0;
}
此时打印的是B show。