面向对象程序设计多态与抽象类.ppt
文本预览下载声明
第5章多态与抽象类 虚函数表 为了达到动态绑定的目的,C++编译器通过某个表格,在执行期间“间接”调用实际上欲绑定的函数,这样的表格称为虚函数表(vtable)。 每一个含有虚函数的类,编译器都为它做一个虚函数表,表中的每一项都指向一个虚函数的地址,实现上是一个函数指针的数组。此外,编译器也会为类加上一个数据成员,是一个指向该虚函数表的指针(vptr)。 在该vtable表中, 保存了该类的所有虚函数的地址(包括从基类继承的)。如果派生类中没有重写基类中声明的虚函数,编译器会采用(最近的)基类的虚函数地址。 创建完该表后,编译器会在类中添加一个vptr指针。对于每一个对象来说,都有一个vptr,vptr指针被初始化为vtable表的起始地址。 一旦这些工作完成,就建立了每个对象所对应的虚函数的调用路径,为多态的实现准备了条件。 当通过对象指针或引用调用虚函数时,将先获取对象的vptr,通过vptr找到虚函数表,再找出正确的虚函数地址,从而实现多态。 注意 虚函数表中的内容依据类中的虚函数声明次序,一一填入函数指针。 派生类会继承基类的虚函数表(以及其它所有可以继承的成员),如果我们在派生类中重写了虚函数,虚函数表也受到了影响,表中每一项所指向的函数地址将不再是基类中的函数地址,而是派生类的函数地址,如果没有重写,则采用(最近的)基类的虚函数地址。 在类对象的内存中,首先是该类的虚函数表指针vptr,然后才是对象的其他数据成员。 将一个类的成员函数定义为虚函数有利于编程,尽管它会引起一些额外的开销。 那么是否所有成员函数都可以声明为虚函数呢? 一般来说,可将类簇中具有共性的成员函数声明为虚函数,而具有个性的函数没有必要声明为虚函数。但是下面的情况例外: (1)静态成员函数不能声明为虚函数。因为静态成员函数不属于某一个对象,没有多态性的特征。 (2)构造函数不能是虚函数。构造函数是在定义对象时被调用,完成对象的初始化,此时对象还没有完全建立。虚函数作为运行时的多态性的基础,主要是针对对象的,而构造函数是在对象产生之前运行的。所以,将构造函数声明为虚函数是没有意义的。 (3)内联成员函数不能声明为虚函数。因为内联函数的执行代码是明确的,在编译时已被替换,没有多态性的特征。如果将那些在类声明时就定义内容的成员函数声明为虚函数,此时函数不是内联函数,而以多态性出现。 (4)析构函数可以是虚函数,且往往被定义为虚函数。一般来说,若某类中有虚函数,则其析构函数也应当定义为虚函数。 特别是需要析构函数完成一些有意义的操作,如释放内存时,由于实施多态性时是通过将基类的指针指向派生类的对象来完成的,如果删除该指针,就会调用该指针指向的派生类的析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象才被完全释放。 因此,析构函数常被声明为虚函数。 下面介绍虚析构函数。 虚析构函数 在C++中,不能声明虚构造函数,但是可以声明虚析构函数,声明格式如下: virtual ~类名(); 如果一个类的析构函数是虚函数,那么,由它派生的所有子类的析构函数也是虚函数。 析构函数被声明为虚函数后,就能保证通过基类指针调用它所指向的派生类对象的析构函数,从而对不同的派生类对象进行清理工作。 class A { public: virtual ~A() { coutcall A::~A()endl; } }; class B:public A { char* buf; public: B(int i) { buf=new char[i]; } virtual ~B() { delete[] buf; coutcall B::~B()endl; } }; void fun(A* a) { delete a; } int main() { A* a=new B(10); fun(a); return 0; } 程序的运行结果是: call B::~B() call A::~A() 如果类A中的析构函数不定义为虚函数,则运行结果为: call A::~A() 5.6 抽象类和纯虚函数 抽象类是为了抽象和设计的目的而建立的,它是一种特殊的类,专门作为基类派生新类。抽象类的主要作用是将有关的派生类组织在一个继承层次结构中,由抽象类为它们提供一个公共的根,相关的派生类就从这个根派生出来。抽象类提供了不同派生类的统一接口,将实现的责任交给了派生类。通过抽象类,可以为一个类簇建立统一的操作接口,实现多态。这个公共接口就是纯虚函数。 含有纯虚函数的类就是抽象类。抽象类自身不能被实例化,只能通过派生类实例化。 纯虚函数的定义 纯虚函数是指那些在基类中无法实现或不需要实现,而在派生类中再给出具体实现的函数。即纯虚函
显示全部