(C/C++学习)5.C++中的虚继承-虚函数-多态解析

说明:在C++学习的过程中,虚继承-虚函数经常是初学者容易产生误解的两个概念,它们与C++中多态形成的关系,也是很多初学者经常产生困惑的地方,这篇文章将依次分别对三者进行解析,并讲述其之间的联系与不同。

 

一.虚继承

1.在多继承中,对于多个父类的数据及函数成员,虽然有时候把他们全部继承下来是有必要的,比如当这些成员都不同的时候。但在大多数的情况下,比如当多个父类之中的成员有重叠的地方时,因为保留多份数据成员的拷贝,不仅占有了较多的存储空间,还增加了访问的难度(由于继承了来自多个父类的同名数据成员,访问时需要加上父类的作用域,比如“父类名::成员”),因此,在实际的继承中是没必要的。而虚继承则可以完美的解决这一问题。

2.在虚继承中,被虚继承的类叫做虚基类,虚基类是需要设计和抽象的,它应当提取多继承父类中重叠的部分作为成员,虚继承是对继承的一种扩展。

示例1:

  1 #include<iostream>
  2 using namespace std;
  3 
  4 class furniture
  5 {
  6 public:
  7     furniture(float l,float wi,float we)
  8         :len(l),wid(wi),weight(we)
  9     {}
 10     void dis()
 11     {
 12         cout<<"len = "<<len<<endl;
 13         cout<<"wid = "<<wid<<endl;
 14         cout<<"weight="<<weight<<endl;
 15     }
 16 protected:
 17     float len;
 18     float wid;
 19     float weight;
 20 };
 21 
 22 //+++++++++++++++++++++++++
 23 
 24 class bed:virtual public furniture
 25 {
 26 public:
 27     bed(float l,float wi,float we)
 28         :furniture(l,wi,we)
 29     {}
 30 
 31     void sleep(){cout<<"go to sleep!!!!!"<<endl;}
 32 };
 33 
 34 //+++++++++++++++++++++++++
 35 
 36 class sofa:virtual public furniture
 37 {
 38 public:
 39     sofa(float l,float wi,float we)
 40         :furniture(l,wi,we)
 41     {}
 42 
 43     void sit(){cout<<"go to have a rest!!!!!"<<endl;}
 44 };
 45 
 46 //+++++++++++++++++++++++++
 47 
 48 class sofabed:public bed,public sofa
 49 {
 50 public:
 51     sofabed(float l,float wi,float we)
 52         :furniture(l,wi,we),bed(1,2,3),sofa(1,2,3)
 53     {}
 54 };
 55 
 56 int main()
 57 {
 58     bed b(1,2,3);
 59     b.sleep();
 60     b.dis();
 61     sofa s(2,3,4);
 62     s.sit();
 63     s.dis();
 64     sofabed sb(4,5,6);
 65     sb.sleep();
 66     sb.sit();
 67     sb.dis();
 68     return 0;
 69 }
 70 
查看代码

 

程序运行结果:

1获

在本例中,如果仅仅采用的是多继承而非虚继承,如下代码所示:

  1 #include<iostream>
  2 using namespace std;
  3 
  4 class bed
  5 {
  6 public:
  7     bed(float l,float wi,float we)
  8         :len(l),wid(wi),weight(we)
  9     {}
 10 
 11     void sleep()
 12     {
 13         cout<<"go to sleep!!!!!"<<endl;
 14     }
 15 
 16     void dis()
 17     {
 18         cout<<"len = "<<len<<endl;
 19         cout<<"wid = "<<wid<<endl;
 20         cout<<"weight = "<<weight<<endl;
 21     }
 22 protected:
 23     float len;
 24     float wid;
 25     float weight;
 26 };
 27 //+++++++++++++++++++++++++++++
 28 class sofa
 29 {
 30 public:
 31     sofa(float l,float wi,float we)
 32         :len(l),wid(wi),weight(we)
 33     {}
 34     void sit()
 35     {
 36         cout<<"go to have a rest!!!!!"<<endl;
 37     }
 38     void dis()
 39     {
 40         cout<<"len = "<<len<<endl;
 41         cout<<"wid = "<<wid<<endl;
 42         cout<<"weight = "<<weight<<endl;
 43     }
 44 protected:
 45     float len;
 46     float wid;
 47     float weight;
 48 
 49 };
 50 //+++++++++++++++++++++++++++
 51 class sofabed:public bed,public sofa
 52 {
 53 public:
 54     sofabed(float l,float wi,float we)
 55         :bed(l,wi,we),sofa(l,wi,we)
 56     {}
 57 };
 58 //+++++++++++++++++++++++++++
 59 int main()
 60 {
 61     bed b(1,2,3);
 62     b.sleep();
 63     b.dis();
 64     sofa s(2,3,4);
 65     s.sit();
 66     s.dis();
 67     sofabed sb(5,6,7);
 68     sb.sit();
 69     sb.sleep();
 70     sb.sofa::dis();
 71     sb.bed::dis();
 72     return 0;
 73 }
查看代码

 

则sb.dis()就有问题了;因为它产生了二义性,编译器不知道该调用哪一个父类的成员函数,而正确做法是加上父类的作用域,这无疑是增加了访问了难度。

结论:多继承带来的数据存储多份,占用内存空间较多,并且访问不便(作用域的增加),采用虚继承可以解决这一问题。

 

二.纯虚函数

1.纯虚函数的格式:

  1 class A
  2 {
  3     virtual void func() = 0;
  4 }

2.含有纯虚函数的类为抽象基类,不可创建对象,其存在的意义就是被继承,提供族类的公共接口,

3.纯虚函数只有声明,没有实现,被初始化为0,

4.如果一个类中声明了纯虚函数,而在派生类中没有对该函数定义,则该函数在派生类中仍然为纯虚函数,派生类仍然为纯虚基类,

5.含有虚函数的类,析构函数也应该声明为虚函数,这样在delete父类指针的时候,才会调用子类的析构函数,实现完整析构,

  1 #include<iostream>
  2 using namespace std;
  3 
  4 class A
  5 {
  6 public:
  7     A()
  8     {
  9         cout<<"A(){}"<<endl;
 10     }
 11     virtual ~A()
 12     {
 13         cout<<"~A(){}"<<endl;
 14     }
 15     virtual void func() = 0;
 16 };
 17 class B:public A
 18 {
 19 public:
 20     B(){cout<<"B(){}"<<endl;}
 21     ~B(){cout<<"~B(){}"<<endl;}
 22     virtual void func()
 23     {
 24         cout<<"B.func(){}"<<endl;
 25     }
 26 };
 27 int main()
 28 {
 29     A*pa = new B;
 30     pa->func();
 31     delete pa;
 32     return 0;
 33 }
 34 
查看代码

程序运行结果:

2获

注意:若在此例中,没有将含有虚函数的父类析构函数声明为虚函数,则将不会调用子类的析构函数~B()实现完整析构。

 

三.多态的实现

1.C++中的多态指的是由于继承而产生的相关的不同的类,其对象对同一消息会做出不同的反应。

2.多态实现的前提是赋值兼容,赋值兼容的内容如下:

    a.子类的对象可以赋值给基类的对象,

    b.子类的对象可以赋值给基类的引用,

    c.子类对象的地址可以赋值给基类的指针(一般用于动多态的实现),

    d.在赋值后,子类对象就可以作为基类对象使用,但只能访问从基类继承的成员.

3.动多态的实现条件:

    a.父类中有虚函数,

    b.子类override(覆写)父类中的虚函数,

    c.将子类的对象赋值给父类的指针或引用,由其调用公用接口.

  1 #include<iostream>
  2 using namespace std;
  3 
  4 class Shape
  5 {
  6 public:
  7     virtual void draw() = 0;
  8 };
  9 //+++++++++++++++++++
 10 class Circle:public Shape
 11 {
 12 public:
 13     void draw()
 14     {
 15         cout<<"Circle"<<endl;
 16     }
 17 };
 18 //+++++++++++++++++++
 19 class Rect:public Shape
 20 {
 21 public:
 22     void draw()
 23     {
 24         cout<<"Rect"<<endl;
 25     }
 26 };
 27 int main()
 28 {
 29     Circle c;
 30     Rect r;
 31     Shape *p = &c;
 32     p->draw();
 33     p = &r;
 34     p->draw();
 35     return 0;
 36 }
 37 
查看代码

注意:C++中的多态一般指动多态,其实C++中函数的重载也是一种多态现象,其通过命名倾轧在编译阶段决定,故称为静多态;而动多态一般是在父子类中在运行阶段决定的。

posted @ 2018-09-28 14:26  退後。  阅读(1215)  评论(2编辑  收藏  举报