C++中有关多态和继承的那些事

     今天就主要和大家分享下多态和继承的那些事,我们先来看百度百科是如何对于多态和继承下定义的。

    多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描述——多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自“Delphi4 编程技术内幕”)。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数(Virtual Function) 实现的。

    C++继承:通过继承机制,可以利用已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员。我们称已存在的用来派生新类的类为基类,又称为父类。由已存在的类派生出的新类称为派生类,又称为子类。

   上述则是百度百科对于继承和派生的定义,为了大家更好的理解,我下面举个例子来更好的说明问题:

    首先我画出了基本的类图:

      这张图大概就表示了一种通信方式就是继承通信,从图上可以直观的看出Base为基类,在基类里面并没有定义数据只有一个叫Do的方法,类B、类A、类C 都继承于Base。它们在继承的同时还有自己的方法就是do( ).

    接下来我们来看基类Base的具体实现:

//基类
class Base{
    public:
        virtual void Do() = 0;    //纯虚函数
        virtual ~Base(){
            cout<<"Base des"<<endl;
        }
        
};

 在基类中我们并没有封装数据,只有一个纯虚函数Do提供 一个接口,还有一个就是析构函数。

  下来我们来看其他类的实现:

//类A
class A :public Base{
    public:
        void Do(){
            cout<<"A's Do"<<endl;
        }
        ~A(){
            cout<<"A des"<<endl;
        }
};
//类B
class B :public Base{
    public:
        void Do(){
            cout<<"B's Do"<<endl;
        }
        ~B(){
            cout<<"B des"<<endl;
        }
};
//类C
class C :public Base{
    public:
        void Do(){
            cout<<"C's Do"<<endl;
        }
        ~C(){
            cout<<"C des"<<endl;
        }
};

//类My
class My{
    private:
        Base * pb;
    public:
        My(Base *t){
            pb = t;
        }
        void Do(){
            pb ->Do();    //多态
        }
        ~My(){
            
            cout<<"My des"<<endl;
            delete pb;
        }
};

客户端:

//客户端
//
//                +---------+      +--------+
//    my -------->|  pb     |----->|        |
//                +---------+      +--------+
//                                     (B)
int main(int argc,char **argv)
{
    My *my = new My(new B());
    my->Do();
    delete my;
    return 0;
}

首先来看一下执行的结果:

    下面给大家分析为什么会出现这个结果:

           我们来看客户端的程序:

               (1)首先用new创建类My的对象,在类My的实现中,把基类Base指针作为它的数据成员,那么在类My的构造函数中就要初始化pb指针,我们可以看到在类My的构造函数中,对pb进行了初始化,所以在客户端创键类My的对象时,就要传递一个基类Base的指针,我们看到在创建时仍用new创建了类B的对象,所以创建类My的对象时,就会调用类My的构造,将会把一个类B的指针赋给基类,这样就使得基类指针指向了派生类B。

                (2)然回用对象my去调自己的方法Do(),因为pb是个基类指针指向了派生类B,所以pb->Do( )就会调用类B的Do( )方法,所以就会在屏幕上打印B's Do

                 (3)在客户端也给大家画出了内存图,当执行完程序准备释放时,我们必须先释放pb所指向的单元,然后在释放my指针,

当客户端发出delete my指令时,程序首先要调用类My的析构函数,所以才会打印出My des,打印完之后,在析构函数里又执行了delete pb 指令,pb是一个指向类B 的基类指针,由于在基类Base中把析构函数也设置为了虚函数,由于基类指针pb无法完全映射派生类B的全部内存(因为派生类不仅从基类继承,还可以由自己的成员和方法,在内存中的低地址为基类成员,然后高地址是自己类的,也就是说派生类中的继承顺序是先基类然后是自己),所以析构时要先释放派生类,再释放基类,所以屏幕上就会先出现

B des,然后出现Base des。

      我们再思考一下,如果将基类Base中析构函数中的virtual关键字去掉之后,程序会发生什么呢??

     上图就是,去掉基类Base的析构函数的virtual,即就是基类的析构函数不具有虚性质,我们来分析一下,当客户端发出delete my的指令时,就会调用类My的析构函数,所以屏幕上还会打印出My des,打印完之后就遇到了delete pb,pb是一个指向派生类的基类的指针,在内存中只会映射基类的那部分,映射不到自己的析构函数,所以也就只调用了基类的析构函数,这是存在内存泄露的!!!

  下面给大家说明基类和派生类之间的数据访问:

     

#include <iostream>
using namespace std;

class Base{
    private:
        int data;
        Base * next;
    public:
        Base(Base *t){
            data = 0;
            next = t;
        }
        Base(int data,Base *t){
            this->data = data;
            next = t;
        }
        virtual void fun(){
            cout<<"data = "<<data<<endl;
            if(next){
                next->fun();
            }
        }
        virtual ~Base(){
            cout<<"Base des"<<endl;
        }
};

class D1:public Base{
    public:
        D1(Base *t):Base(t){}
        D1(int data,Base*t):Base(data,t){}

        ~D1(){
            cout<<"D1 des"<<endl;
        }
};

class D2:public Base{
    public:
        D2(Base *t):Base(t){}
        D2(int data,Base*t):Base(data,t){}

        ~D2(){
            cout<<"D2 des"<<endl;
        }
};

class D3:public Base{
    public:
        D3(Base *t):Base(t){}
        D3(int data,Base*t):Base(data,t){}

        ~D3(){
            cout<<"D3 des"<<endl;
        }
};

int main(int argc,char ** argv)
{
    D3 * pd3 = new D3(5,NULL);
    D2 * pd2 = new D2(8,pd3);
    Base * pb = new D1(11,pd2);
    pb->fun();
    delete pb;
    delete pd2;
    delete pd3;
    return 0;
}
 我们先来看其执行结果:

C++中有关多态和继承的那些事_第1张图片     

     客户端的实现的功能就是一次调用将所有派生类的数据全部打印出来。

     我们首先来看整个程序的调用过程:

        (1)客户端首先创建类D3的对象,我们都知道创建对象就要调构造函数,D3是继承于基类Base,那么调构造时先要调用基类的构造,由于客户端在创建对象时传入的是两个参数,所以会调用D3的第二个构造函数,由于D3是继承来的,所以会先去调用基类Base的构造函数,调用完之后pd3的data = 5,next = NULL,同理创建对象pd2和pb,

     C++中有关多态和继承的那些事_第2张图片

   创建完成就会形成一个链表。

     (2)程序执行pb ->fun( );由于pb是基类Base指针指向派生类D1,根据多态性就会调用类D1的fun(),但是类D1中没有定义fun()的方法,所以就会调用基类的fun()方法,在基类的fun()方法中就会先打印出D1的data,所以屏幕上就会出现11,然后判断D1的next是否为空,next是一个基类指针,同样利用多态,因为D1的next中保存的是派生类D2的指针,所以next ->fun( ),就会调用类D2的fun()函数,同样因为类D2没有fun( )函数,所以又会调用基类的fun()函数,在屏幕上打印8,因为类D2的next保存着类D3的指针,接着又会调到类D3中去找fun()函数,同样因为类D3中没有,就会到基类中执行fun()函数,执行完之后,就该返回到类D3中了,因为D3的next为空,从D3中又返回到基类中,再从基类中返回到D2中,又返回到基类中,最后再返回到客户端。

       下面用图示来说明:

C++中有关多态和继承的那些事_第3张图片


    我们再思考一下,如果将基类的析构函数的虚性质去掉之后,程序会怎样呢??

  C++中有关多态和继承的那些事_第4张图片

   和之前的分析一样,会导致内存泄露。

    在继承中对于基类的数据我们应该是私有化好呢还是受保护好呢??对于这个问题没有哪个好,哪个不好,这要根据具体的需求而言,如果在派生类中对于基类的成员的操作都一样时,这时我们可以将这些操作封装到基类里,私有化比较好,就比如上述的例子,但是对于派生类来说,每一个类对于基类的成员操作都不同时,这时就要设置成受保护类型的,在每个派生类里单独进行实现,因为在共有继承中,派生内可以继承私有的数据但是无法访问访问基类中私有的数据,所以要设置成受保护类型,这样在派生类才能访问。

你可能感兴趣的:(C++中有关多态和继承的那些事)