1、。下一章再讲。 函数覆盖:这是本章所要讨论的。一、静态联编和动态联编联编是指一个计算机程序自身彼此关联的过程。按照联编所进行的阶段不同,可分为两种不同的联编方法:静态联编和动态联编。1 静态联编 静态联编是指联编工作出现在编译连接阶段,这种联编又称早期联编,因为这种联编过程是在程序开始运行之前完成的,在编译时就解决了程序中的操作调用与执行该操作代码间的关系。下面举一个静态联编的例子,该例子是图形面积的计算。例1:class Point /该类计算点的面积 private: double x, y; /点的坐标public:Point(double i, double j) x=i; y=j;
2、/构造函数double area() const return 0.0; /计算面积;class Rectangle:public point /计算矩型面积 double w, h; /矩型长、宽/构造函数,包含对父类成员变量的初始化Rectangle(double i, double j, double k, double l);/覆盖了父类同名函数area()/ area() const表示函数内不可更改成员变量的值,/保证在该函数内使用的成员变量只是传值,并不发生值的变化double area() const return w*h; Rectangle:Rectangle(double
3、 i, double j, double k, double l):point(i, j) w=k; h=l;void fun(Point &s) /全局函数,传递对象引用couts.area(); void main() Rectangle rec(3.0, 5.2, 15.0, 25.0);fun(rec); 该程序的运行结果为:0。算出了点的面积而不是矩形面积,为什么?虽然实例化了一个矩形对象Rectangle:但是,在调用fun(rec)时,由于Rectangle类继承了Point类,因此Rctangle对象rec实例化时肯定要实例化一个Point,而函数void fun(Point
4、&s)的形参类型正好是Point对象引用,因此编译器在编译时把函数实参绑定到Point对象的引用上。这是静态联编的结果,导致程序输出了所不期望的结果。虽然上面的例子将void fun(Point &s)改为void fun(Rectangle &s)可轻易解决问题,但这不符合多态“一个概念,多种实现”的思想,我们希望的是;维持Point这“一个概念”,让void fun(Point &s)作为对外服务的接口,持有一个指向Point的对象&s,而在具体的矩型、多边型、圆型对象中,动态的按需调用所需的对象。这需要动态联编技术。2动态联编 动态联编实际可以在程序运行时动态识别所需调用的对象。c+规定
5、动态联编是在虚函数的支持下实现的。二、虚函数1 虚函数基本特点如果某类中的一个成员函数被说明为虚函数,这就意味着该成员函数在派生类中可能有不同的实现。当使用一个指针或引用所标识对象操作该成员函数时,对该成员函数调用采取动态联编方式。 声明一个成员函数为虚函数,只需在函数声明前加virtual关键字即可。C+对virtual函数进行动态联编。下面将例1的area()声明为虚函数:例2(为简化,删掉了构造函数):class Point /该类计算点的面积 virtual double area() const return 0.0; /虚函数 /矩型长、宽 /子类也可以不写virtualvirtu
6、al double area() const return w*h; ; 输出结果是375。因为area()声明为虚函数,C+对其动态联编,运行时确定,将fun(rec)中的&s形参绑定到子类对象rec。 形象的说,动态联编是“自下向上”绑定对象的,先看看有没有本类对象,有则绑定本类对象,没有则从最低一级逐级向上绑定,如例2所示。而静态联编是“自上向下”先看看本类有没有合适对象,有则绑定本类对象,没有则从最高一级开始向下查找绑定对象的,如例1所示。2 运行时多态的好处可以只曝露较抽象的基类,而隐藏具体类的实现,用户调用抽象的服务得到的是具体的服务。运行时多态也有利于实现分布式智能化服务。这些好
7、处是与具体语言无关的。对例2作伪代码扩展,可以看出运行时多态的好处。例3(伪代码):class POINT virtual area() /抽象服务类/以下是具体服务类,继承了POINTclass SHAPE1 : POINTvirtual area() class SHAPE2 : POINT virtual area() class SHAPE3 :fun(Point &s) /一个抽象服务接口 s.area() SHAPE1 S1;fun(S1) ;/具体服务,系统动态确定SHAPE1 S2;fun(S2) ;SHAPE1 S3;fun(S3) ;用户只需知道fun()的位置,知道fun
8、()提供计算面积的服务,需要计算具体图形面积时,创建一个该图形对象即可,不必关心这个具体图形面积是哪个对象计算的。这是一种非常优雅的体系结构。3 进一步理解虚函数C+动态联编必需的条件是: 要建立子类型关系。 要有虚函数的说明,只有虚函数才可以动态联编。 要虚函数发挥作用,必须用基类的指针(或引用)指向派生类的对象(比如例2中的fun(Point &s), 只有地址才能体现多态性,要用指针(或引用)调用虚函数。如果在一个函数内调用虚函数,如果默认的this指针有效,是符合本条要求的。例4符合上述要求的动态联编:#include class A public: virtual void act1
9、(); /有虚函数 void act2() act1(); /类成员调用虚函数,有默认的this指针this-/类函数调用虚函数,显式的thisA: /使用了类限定符,this无效,静态联编 void A:act1() coutact1() called; class B: public A /建立子类型关系 virtual void act1();/有虚函数 ; void B:act1() coutact1() called“; void main() B b; b.act2();运行结果是:act1() called B:act1() called A:act1() called3 构造函
10、数调用虚函数在“第9章 继承”曾讲过,派生类构造函数的调用顺序如下: 先调基类的构造函数产生基类对象 再调用派生类构造函数当构造函数内调用虚函数时,仍沿用上述规则,但对虚函数的调用采用的是静态联编而不是动态联编,为什么会这样,先看例5:例5:构造函数中调用虚函数。class A A() /无参构造函数 virtual void f() coutf() called. class B : public A B() f(); /构造函数中调用虚函数void g() f();class C: public B C() virtual void f() coutg()开始绑定对象时,编译器知道的是,g
11、()是一个声明为virtual的成员函数,而且其入口地址放在表格(无论是vtalbeA表还是 vtalbeB表)的第2项,那么编译器编译这条语句的时候就如是转换:call *(pa-vptr)2),(这一行可看作伪代码)。这一项放的是B:g()的入口地址,则就实现了多态。(注意b的vptr指向的是B的虚表vtableB)。C+标准只要求用虚标机制实现多态,至于虚指针vptr到底放在一个对象布局的哪里,标准没有要求,每个编译器自己决定。如果继承体系的基类的virtual成员不多,而且在派生类要重写的部分占了其中的大多数时候,用的虚函数机制是比较好的;但是如果继承体系的基类的virtual成员很多
12、,或者是继承体系比较庞大的时候,而且派生类中需要重写的部分比较少,那就用另一种多态实现机制:“名称查找表”效率会高一些,很多的库都是这样的。三、纯虚函数和抽象类 JAVA语言中有抽象类和接口的语法。JAVA引入接口的目的主要是将抽象类与子类间的is a kind关系与接口与实现类的关系区分开。 C+有抽象类而无接口的语法。但C+的抽象类也可当作接口使用。1纯虚函数纯虚函数是一种特殊的虚函数,它的一般格式如下: class 类名 virtual 返回类型 函数名(参数表)=0; 在许多情况下,在基类中不能对虚函数给出有意义有实现,而把它说明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函
13、数的作用。2抽象类 带有纯虚函数的类称为抽象类。抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限。 抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共的根,相关的子类是从这个根派生出来的。 抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类。 抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。本章小结: 动态联编下的多态性(主要体现在函数覆盖)是不太容易理解的,可以分成几个层次理解它:1 设计概计和思想,本章的例3作了较好的说明。这是与语言无关的,也是最重要的。2 知道动态联编是“自下向上”而静态联编是“自上向下”绑定对象,对于阅读和编写程序很有帮助。这虽是实现层面的东西,但几乎也与语言无关,因为面向对象语言都按此顺序绑定对象。3 具体到C+,通过理解虚表,理解动态联编是怎样实现的,为什么需要指针或引用调用虚函数。本章课后习题:理解例1-例5的代码
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1