第六周 多态
1.虚函数和多态的基本概念
2.多态实例:魔法门之英雄无敌
3.更多多态程序实例
4.多态的实现原理
5.虚析构函数、纯虚函数和抽象类
4.多态的实现原理
“多态”的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定 ---- 这叫“动态联编”。“动态联编” 底是怎么实现的呢?
例子:
class Base {
public:
int i;
virtual void Print() { cout << "Base:Print" ; }
};
class Derived : public Base{
public:
int n;
virtual void Print() { cout <<"Drived:Print" << endl; }
};
int main() {
Derived d;
cout << sizeof( Base) << ","<< sizeof( Derived ) ;
return 0;
}
输出结果: 8, 12或12,16,也可能是其他,有对齐问题
为什么都多了4个字节?多态实现的关键 — 虚函数表。
每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表,这个虚函数表是编译器自动生成的,当可执行程序装到内存里的时候,这个类会多一块内存,列出这个类的虚函数地址。当声明这个类的一个对象时,就会自动多出一块4个字节的内存,用来存放一个指针,指向这个类的虚函数表的地址,这块内存位于对象内存的头部。也就是说,对象有额外的4个字节存放一个指向其所属类的虚函数表的地址,访问这个虚函数表就可以得到该类所有虚函数的地址,进而调用该类的虚函数。
pBase = pDerived;
pBase->Print();
多态的函数调用语句被编译成一系列根据基类指针所指向的(或基类引用所引用的)对象中存放的虚函数表的地址,在虚函数表中查找虚函数地址,并调用虚函数的指令。
这里的话pBase指向的是对象pDerived,所以,是在对象pDerived的内存中找到指向的虚函数表,再在这个表里找到虚函数Print()的地址,然后调用。这样的结果就是pBase->Print();没有调用pBase所属Base类的虚函数Print(),而是pBase所指向对象pDerived所属Derived类的虚函数Print()。
多态的代价
(1)空间上:每个有虚函数的对象都多出来4个字节指向其所属类的虚函数表地址;
(2)时间上:查虚函数表需要额外的时间开销。
#include <iostream>
using namespace std;
class A {
public: virtual void Func() { cout << "A::Func" << endl; }
};
class B:public A {
public: virtual void Func() { cout << "B::Func" << endl; }
};
int main() {
A a;
A * pa = new B();
pa->Func();//访问类B的虚函数表地址
//64位程序指针为8字节
long long * p1 = (long long * ) & a;//类A的虚函数表地址
long long * p2 = (long long * ) pa;//类B的虚函数表地址
* p2 = * p1;//用类A的虚函数表地址替换类B的虚函数表地址
pa->Func();//访问类A的虚函数表地址
return 0;
}
输出:
B::Func
A::Func