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

多态是对于同一消息做出不同的反应。相应于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.
", tri.area());

    Ccircle cir(4);
    printf("The area of circle is %f.
", cir.area());

    Cshape *shape1 =  &tri;   // a base pointer to the tri's memory
    printf("The area of shape1 is %f.
", shape1->area());

    Cshape &shape2 = cir;   // a base var's memory value is cir's value
    printf("The area of shape2 is %f.
", shape2.area());

    return 0;  
}

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

(2)虚函数
由此可见。类继承是一种挺笨的方法。要实现动态联编。能够通过虚函数实现。所谓的动态联编就是依据基类所引用的派生类对象,动态来获取该派生类对象的函数。

方法非常easy。在类继承的基础上,在基类的函数前面加上“virtual”关键字就可以,该函数在各个派生类中仍然是虚函数,仅仅只是省略了virtual关键字而已。

须要注意的是:

  • virtual修饰的必须是基类的函数成员。而非友元函数。友元函数仅仅是表达与该类有友好共享数据成员的关系。并不属于该类。因此不能被继承。
  • virtual不能修饰static函数成员。

    static函数成员仅能訪问该类中的静态数据成员,但能够被该类全部对象共享。easy引起混乱。

  • 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.
", tri.area());

    Ccircle cir(4);
    printf("The area of circle is %f.
", cir.area());

    Cshape *shape1 =  &tri;   // a base pointer to the tri's memory
    printf("The area of shape1 is %f.
", shape1->area());

    Cshape &shape2 = cir;   // a base var's memory value is cir's value
    printf("The area of shape2 is %f.
", shape2.area());

    return 0;  
}

依据上述介绍。shape1和shape2分别引用了tri和cir对象,其area函数应该是派生类对象的函数,而非基类函数。

执行结果:
这里写图片描写叙述

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

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

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

执行结果跟虚函数的结果是一样的。

包括至少一个纯虚函数的类叫做抽象类,是不能直接声明一个对象的,仅仅有在定义了派生类对象后才干通过引用该对象来声明。

下一篇解说重载。

原文地址:https://www.cnblogs.com/wzjhoutai/p/7290307.html