(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 #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
程序运行结果:
注意:若在此例中,没有将含有虚函数的父类析构函数声明为虚函数,则将不会调用子类的析构函数~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++中函数的重载也是一种多态现象,其通过命名倾轧在编译阶段决定,故称为静多态;而动多态一般是在父子类中在运行阶段决定的。