C++的多态性分为两种:静态多态(编译时多态)和动态多态(运行时多态)。静态多态主要通过重载和模板来实现,动态多态是子类覆盖父类的虚函数,定义一个父类的指针,使指针指向子类实例,这样,父类就可以访问子类的方法,如果把父类的指针指向不同的子类实例,父类就可以访问不同的方法。
class Base
{
public:
int data;
void myFunc() { cout << "myFunc" << endl; }
virtual void f() { cout << "Base::f()" << endl; }
virtual void g() { cout << "Base::g()" << endl; }
virtual void h() { cout << "Base::h()" << endl; }
};
class Derive : public Base
{
public:
virtual void g() { cout << "Derive::g()" << endl; }
};
Base b;
Base *ptr = new Derive();
ptr->g(); //Derive::g()
在创建带有虚函数的类的实例时,会创建一个指向虚函数表的指针,这个指针被存在实例的开始处,虚函数表中保存着虚函数的地址,如图:
我们可以这样获取到虚函数的地址:&b表示类的首地址,由于类内存表中第一个位置存的是指针,所以先把地址强制转换成指针类型,就可以取出第一个值
*(int*)(&b)
,这个值就是指向虚函数的指针vptr(g++中指向虚函数表的指针放在开始处),vptr就是虚函数表的首地址,同理,虚函数表中存的是指针,所以强制转换后取值
*(int*)*(int*)(&b)
就是函数 f 的地址,
*((int*)*(int*)(&b)+1)
就是 g 的地址。要想输出虚函数,需要强制转换成函数指针。
typedef void(*pfunc)(void);
Base b;
pfunc p;
p = (pfunc)*((int*)*(int*)(&b)+1); //强制转换成函数指针
p(); //Base::g();
注:忽略了一点,64位时指针是8个字节,而不是4个字节,所以,上述int*中的int改为long更恰当。
子类继承基类时,虚函数表中会包含子类和基类的全部虚函数
class Derive : public Base
{
public:
virtual void df() { cout << "Derive::df()" << endl;
virtual void dg() { cout << "Derive::dg()" << endl;
virtual void dh() { cout << "Derive::dh()" << endl;
}
如果子类的虚函数覆盖了父类的虚函数,虚函数表中父类的虚函数将会被子类的虚函数替代。
class Derive : public Base
{
public:
virtual void f() { cout << "Derive1::f()" << endl;
virtual void dg() { cout << "Derive1::dg()" << endl;
virtual void dh() { cout << "Derive1::dh()" << endl;
}
Derive d;
Base * b = &d;
b->f(); //Derive::f()
所以,前面的例子中,父类的指针Base * ptr = new Derive(),访问 f 方法时(ptr->f()),访问到的是子类的方法。
多重继承时,有几个含有虚函数的父类,就会创建几个指向虚函数表的指针,每个虚函数表都存着对应的父类的虚函数的地址,其中,子类的虚函数地址保存在第一个父类的虚函数表中。
class Base1
{
public:
virtual void f() { cout << "Base1::f()" << endl; }
virtual void g() { cout << "Base1::g()" << endl; }
virtual void h() { cout << "Base1::h()" << endl; }
};
class Base2
{
public:
virtual void f() { cout << "Base2::f()" << endl; }
virtual void g() { cout << "Base2::g()" << endl; }
virtual void h() { cout << "Base2::h()" << endl; }
};
class Derive : public Base1, public Base2
{
public:
virtual void df() { cout << "Derive::df()" << endl;
};
Derive d;
Base1 * ptr1 = &d;
Base2 * ptr2 = &d;
ptr1->f(); //Base1::f()
ptr2->f(); //Base2::f()
两个父类Base1和Base2的指针虽然都指向子类,但是其实地址是不同的,ptr1对应的是vptr1指向的对象,ptr2对应的是vptr2指向的对象。
子类覆盖父类方法时:
class Derive : public Base1, public Base2
{
public:
virtual void f() { cout << "Derive::f()" << endl;
}
Derive d;
Base1 * ptr1 = &d;
Base2 * ptr2 = &d;
ptr1->f(); //Derive::f()
ptr2->f(); //Derive::f()