多态指同一个方法根据其所属的不同对象可以有不同的行为。
下面来看一个例子:
程序运行输出结果: 8, 12
为什么Base 对象的大小是8个字节而不是4个字节,为什么Derived 对象的大小是12个字节而不是8个字节,多出来的4个字节做什么用呢?和多态的实现有什么关系?
每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都放着虚函数表的指针。虚函数表中列出了该类的虚函数地址。多出来的4个字节就是用来放虚函数表的地址的。
每当创建一个包含虚函数的类或者从一个有虚函数的基类中派生一个类时,编译器就为这个类创建一个VTABLE,在这个表中,放置了在这个类中或它的基类中所有声明为virtual的虚函数的地址。然后编译器在这个类中放置VPTR指向相应的VTABLE。VPTR必须在构造函数中被初始化,在VPTR初始化之前,绝对不能调用虚函数。所有的基类对象或者从基类派生出的对象的VPTR都在各自对象的相同位置。所有的VTABLE有相同的顺序,不管何种类型的对象。
C++的函数调用与C一样,都是从右向左进栈的,其间,对象的首地址也即this指针的值被压入栈,正因为调用每个成员函数时this都必须作为参数压进栈,所以成员函数知道它工作在哪个特殊对象上。这样,我们总能看到,在成员函数调用之前压栈的次数等于参数个数加一(除了STatic成员函数,它没有this)。
纯虚函数 :
抽象类就是在类的声明前面加上virtual关键字,为了防止误用抽象类,可以在抽象类中定义纯虚函数,例如virtual void x()=0;
这样做,等于告诉编译器在VTABLE中为函数保留一个间隔,但在这个特定间隔不放地址,只要有一个纯虚函数,则VTABLE就是不完全的,包含纯虚函数的类称为纯抽象基类。
虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。假设我们有下面的类层次:
那么,在使用的时候,我们可以:
A * a = new B();
a->foo(); // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!
这个例子是虚函数的一个典型应用,通过这个例子,也许你就对虚函数有了一些概念。它虚就虚在所谓“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。
如下声明表示一个函数为纯虚函数:
class A
{
public:
virtual void foo()=0; // =0标志一个虚函数为纯虚函数
};
一个函数声明为纯虚后,纯虚函数的意思是:我是一个抽象类!不要把我实例化!纯虚函数用来规范派生类的行为,实际上就是所谓的“接口”。它告诉使用者,我的派生类都会有这个函数。