多态之一(继承和虚函数)

来源:互联网 发布:2017东南大学软件学院 编辑:程序博客网 时间:2024/04/30 10:08

多态是对于同一消息做出不同的反应。对应于C++语法,是同一函数在同样的输入下产生不同的反应。这里的同一函数表示“函数类型、函数名、参数”。多态有三种表现形式:类继承、虚函数和重载。
(1)类继承
类继承是最为基础的一种静态联编的多态方式。联编是通过编译和连接库文件而形成可执行文件的动作。继承是静态的,是因为它不能根据基类所引用的派生类对象而获取该对象对应的函数。
基类不能通过派生类的构造函数直接初始化,虽然派生类反过来可以直接使用基类构造函数进行初始化。但是,基类通过指针或者引用内存的方式引用派生类对象进行初始化。尽管如此,继承的静态性直接导致了基类对象不能引用派生类对象的函数,而是根据“就近优先”原则首先使用基类自身的函数。代码示例如下:

#include <stdio.h> //Standard namespace with many headfiles replaced to its C'using namespace std; //#define is a macro definition to replace PI with number 3.14#define PI 3.14  class Cshape{  //for each self-defined class, "CLASS" is essential    public:      float area()        {   areas =0.0;            return areas;      }    private:       float areas; //it doesn't matter whether areas declared or just return directly in area()};class Ctriangle:public Cshape{    public:      Ctriangle(float high=0.0, float bottom=0.0)  //Constructor      {          this->high = high;          this->bottom = bottom;      }      float area()      {          areas = (float) (high * bottom / 2);          return areas;      }    private:       float high, bottom, areas;};class Ccircle: public Cshape{    public:      Ccircle(float radius=0.0) //Constructor       {          this->radius = radius;      }      float area()      {          return (float) (radius * radius * PI / 2);      }    private:      float radius;};int main(){    Ctriangle tri(6,5);    printf("The area of triangle is %f.\n", tri.area());    Ccircle cir(4);    printf("The area of circle is %f.\n", cir.area());    Cshape *shape1 =  &tri;   // a base pointer to the tri's memory    printf("The area of shape1 is %f.\n", shape1->area());    Cshape &shape2 = cir;   // a base var's memory value is cir's value    printf("The area of shape2 is %f.\n", shape2.area());    return 0;  }

根据上述介绍,shape1和shape2虽分别引用了tri和cir这两个派生类对象,但是其area()函数应该按照“就近优先”原则调用Cshape类的函数,而不是Ctriangle或者Ccircle类的函数。运行结果:
这里写图片描述

(2)虚函数
由此可见,类继承是一种挺笨的方法。要实现动态联编,可以通过虚函数实现。所谓的动态联编就是根据基类所引用的派生类对象,动态来获取该派生类对象的函数。方法很简单,在类继承的基础上,在基类的函数前面加上“virtual”关键字即可,该函数在各个派生类中仍然是虚函数,只不过省略了virtual关键字而已。需要注意的是:

  • virtual修饰的必须是基类的函数成员,而非友元函数。友元函数只是表达与该类有友好共享数据成员的关系,并不属于该类,因此不能被继承。
  • virtual不能修饰static函数成员。static函数成员仅能访问该类中的静态数据成员,但能够被该类所有对象共享,容易引起混乱。
  • virtual只能修饰基类的public或protect函数成员。私有函数成员在派生类中是不能被访问的。
  • virtual可以修饰基类的析构函数,却不能修饰基类的构造函数。在构造函数运行成功之前,任何对象都是不存在的。
  • -

在Cshape的area()函数前加一个virtual关键字,代码如下:

#include <stdio.h> //Standard namespace with many headfiles replaced to its C'using namespace std; //#define is a macro definition to replace PI with number 3.14#define PI 3.14  class Cshape{  //for each self-defined class, "CLASS" is essential    public:      virtual float area()  //without virtual, the results are diff in shape1 & shape2      {   areas =0.0;            return areas;      }    private:       float areas; //it doesn't matter whether areas declared or just return directly in area()};class Ctriangle:public Cshape{    public:      Ctriangle(float high=0.0, float bottom=0.0)  //Constructor      {          this->high = high;          this->bottom = bottom;      }      float area()      {          areas = (float) (high * bottom / 2);          return areas;      }    private:       float high, bottom, areas;};class Ccircle: public Cshape{    public:      Ccircle(float radius=0.0) //Constructor       {          this->radius = radius;      }      float area()      {          return (float) (radius * radius * PI / 2);      }    private:      float radius;};int main(){    Ctriangle tri(6,5);    printf("The area of triangle is %f.\n", tri.area());    Ccircle cir(4);    printf("The area of circle is %f.\n", cir.area());    Cshape *shape1 =  &tri;   // a base pointer to the tri's memory    printf("The area of shape1 is %f.\n", shape1->area());    Cshape &shape2 = cir;   // a base var's memory value is cir's value    printf("The area of shape2 is %f.\n", shape2.area());    return 0;  }

根据上述介绍,shape1和shape2分别引用了tri和cir对象,其area函数应该是派生类对象的函数,而非基类函数。运行结果:
这里写图片描述

虚函数为什么能够实现动态选择派生类对象的函数呢?这是因为基类在定义虚函数时,在内部创建了一张VTable表(虚表)和一个指向表中函数的vptr指针。定义派生类时,派生类对应的函数版本也会被纳入基类的VTable表中,这样所有的area()函数都在同一张表中。在明确了基类引用哪个派生类对象后,vptr指针会移动到VTable表中该派生类所对应的area()版本处,这样就实现动态调用了。

(3)纯虚函数
有时候,在基类中完全不知道函数该怎样定义,只能依据各派生类视情况而定义,此时就需要用到纯虚函数。纯虚函数在基类中相当于一个空函数,它的存在在于提醒各派生类记得在各自类中定义该函数。在基类的定义如下:

class Cshape{
public:
virtual float area()=0;
};

运行结果跟虚函数的结果是一样的。包含至少一个纯虚函数的类叫做抽象类,是不能直接声明一个对象的,只有在定义了派生类对象后才能通过引用该对象来声明。

下一篇讲解重载。

0 0
原创粉丝点击