当前位置: 首页 > news >正文

C++ 多继承与虚基类

转载来自:CSDN insistGoGo  (http://blog.csdn.net/insistgogo)
多继承的定义:
派生类的基类大于一个

语法:

[cpp] view plain copy
  1. class  派生类名:继承方式1 基类名1,继承方式2 基类名2...  
  2. {  
  3.     <派生类新定义成员>  
  4. };  

多重继承与构造函数的关系:

多重继承时构造函数的作用:

1)初始化派生类(自己)

2)调用该派生类所有基类构造函数,并且为所有基类传参(参数个数必须包含所有基类所需参数)

构造函数语法:

[cpp] view plain copy
  1. 派生类构造函数名(总参数表列): 基类1构造函数(参数表列), 基类2构造函数(参数表列), 基类3构造函数(参数表列)  
  2. {  
  3.     //派生类中新增数成员据成员初始化语句  
  4. }  

说明:派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的缺省构造函数。

具体点来说:初始化列表中要包括对 直接基类 + 虚基类 进行调用。

构造函数的执行次序(不含虚基类):

(1)基类:依派生的次序决定,与构造函数中书写顺序无关

(2)子对象的构造函数

(3)派生类的构造函数

析构函数的执行次序:和上述执行顺序相反

注意:

1)析构函数能继承;

2)派生类中要定义自己的析构函数释放在派生中新增的成员;

3)从基类中继承的成员释放,可以通过基类的析构函数实现;

4)激活析构函数的顺序与构造函数缴活顺序相反。

举例:

[cpp] view plain copy
  1. #include <iostream>  
  2. using namespace std;  
  3. class A  
  4. {  
  5. public:  
  6.     A()  
  7.     {  
  8.         cout<<"调用A的构造函数"<<endl;  
  9.     }  
  10. };  
  11. class B  
  12. {  
  13. public:  
  14.     B()  
  15.     {  
  16.         cout<<"调用B的构造函数"<<endl;  
  17.     }  
  18. };  
  19. class C:public A,public B //这里声明顺序决定了调用基类的顺序  
  20. {  
  21. private:  
  22.     A a;  
  23. public:  
  24.     C()  
  25.     {  
  26.         cout<<"调用C的构造函数"<<endl;  
  27.     }  
  28. };  
  29. void main()  
  30. {  
  31.     C c;  
  32.     system("pause");  
  33. }  

运行结果:

调用A的构造函数--C的基类

调用B的构造函数--C的基类

调用A的构造函数--C的对象成员

调用C的构造函数--C自己的构造函数

说明:

1、继承/多重继承一般是公有继承,保护继承/私有继承只是在技术讨论较多,实际使用较少。

多继承中同名覆盖原则

当派生类与基类中有同名成员时:

调用派生类的成员:定义派生类对象,直接调用同名函数即可,而自动屏蔽基类的同名函数。

访问基类中成员:应使用基类名限定。

举例:

[cpp] view plain copy
  1. #include<iostream>  
  2. using namespace std;  
  3. class A  
  4. {  
  5. public:  
  6.     void f()  
  7.     {  
  8.         cout<<"调用A的构造函数"<<endl;  
  9.     }  
  10. };  
  11. class B  
  12. {  
  13. public:  
  14.     void f()  
  15.     {  
  16.         cout<<"调用B的构造函数"<<endl;  
  17.     }  
  18. };  
  19. class C:public A,public B  
  20. {  
  21. public:  
  22.     void f()  
  23.     {  
  24.         cout<<"调用C的构造函数"<<endl;  
  25.     }  
  26. };  
  27. void main()  
  28. {  
  29.     C c;  
  30.     c.f();//覆盖基类中f函数  
  31.     c.B::f();//通过基类名限制访问  
  32.     c.A::f();  
  33.     system("pause");  
  34. }  

多继承带来的二义性:

当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性

即:派生类从同一个基类,沿不同继承方向,得到多个相同的拷贝,不知道要访问哪一个,就产生了二义性。

二义性的常用解决方法:使用作用域运算符(类名::)来解决访问二义性问题使用访问,但是这里的成员都是来源于同一个基类,这时是不能解决问题的,这里就引入虚基类

虚基类:

虚基类的作用:使公共基类只产生一个拷贝,即只对第一个调用的有效,对其他的派生类都是虚假的,没有调用构造函数

使用场合:用于有共同基类的场合

原理:让虚基类的构造函数只执行一次,派生类只得到一套虚基类的成员

语法:

[cpp] view plain copy
  1. class 派生类名:virtual 继承方式 类名  //在派生类定义的时候写。  
  2. {  
  3.   
  4. }  

注意:声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次

虚基类的初始化:与一般多继承的初始化在语法上是一样的,但构造函数的调用次序不同.

派生类构造函数的调用次序:(先虚基类,后基类,再成员对象,最后自身

(1)对虚基类间的构造函数的顺序:根据虚基类间继承的顺序调用

(2)对基类间的构造函数的顺序:根据基类间继承的顺序调用

(3)对成员对象的构造函数的顺序:根据成员对象在类中声明顺序调用

(4)若同一层次中包含多个虚基类,这些虚基类的构造函数按它们说明的次序调用;

(5)若虚基类由非虚基类派生而来,则仍先调用基类构造函数,再调用派生类的构造函数.

举例:

[cpp] view plain copy
  1. class  A :  public B, public C,virtual public D  
  2. {}  
  3. X one;  
  4. 将产生如下调用次序:  
  5. D()  
  6. B()  
  7. C()  
  8. A()  

说明:

1)D是A的虚基类,故先调用D的构造函数

2)在同层次的多个虚基类中,从左到右调用,先B到C

3)基类构造函数调用完后,在调用A的构造函数

举例:使用虚基类和不使用虚基类的说明:

错误代码:

[cpp] view plain copy
  1. #include<iostream>        
  2. using namespace std;  
  3. class A  
  4. {  
  5. protected:  
  6.     int a;  
  7. public:  
  8.     A(int a)  
  9.     {  
  10.         this->a=a;  
  11.     }  
  12. };  
  13. class B1: public A  
  14. {  
  15. public:  
  16.     B1(int a):A(a)  
  17.      {  
  18.      }  
  19. };  
  20. class B2: public A  
  21. {  
  22. public:  
  23.     B2(int a):A(a)  
  24.     {  
  25.         this->a=a;  
  26.     }  
  27. };  
  28. class C:public B1,public B2  
  29. {  
  30. public:  
  31.     C(int a):B1(a),B2(a) //没有使用虚基类,声明时,只写C的直接基类B1和B2,不写虚基类构造函数A  
  32.     {  
  33.   
  34.     }  
  35.     void display()  
  36.     {  
  37.         cout<<"a="<<a<<endl;//使用A::a区分也不行,这里的a是从A得到的,B1和B2都继承到了,到D中就有两份拷贝,都来自A产生歧义  
  38.     }  
  39. };  
  40. void main()  
  41. {  
  42.     D d(1);  
  43.     d.display();  
  44.     system("pause");  
  45. }  

基类的层次图:

正确代码:

[cpp] view plain copy
  1. #include<iostream>  
  2. using namespace std;  
  3. class A  
  4. {  
  5. protected:  
  6.     int a;  
  7. public:  
  8.     A(int a)  
  9.     {  
  10.         this->a=a;  
  11.     }  
  12. };  
  13. class B:virtual public A  
  14. {  
  15. public:  
  16.     B(int a):A(a)  
  17.      {  
  18.      }  
  19. };  
  20. class C:virtual public A  
  21. {  
  22. public:  
  23.     C(int a):A(a)  
  24.     {  
  25.         this->a=a;  
  26.     }  
  27. };  
  28. class D:public B,public C  
  29. {  
  30. public:  
  31.     D(int a):B(a),C(a),A(a)//使用虚基类,声明时是 D的直接基类B和C + 直接基类的共同基类A  
  32.     {  
  33.   
  34.     }  
  35.     void display()  
  36.     {  
  37.         cout<<"a="<<a<<endl;  
  38.          //使用虚基类时,A只执行一次,调用B的时候调用一次虚基类,调用C时,就没有调用A了,D中的a也只有一个拷贝,因而不产生歧义  
  39.     }  
  40. };  
  41. void main()  
  42. {  
  43.     D d(1);  
  44.     d.display();  
  45.     system("pause");  
  46. }  

使用虚基类后的层次图:

注意:

1、派生类构造函数初始化列表对虚基类的处理:

有虚基类和没费基类两种情况下,派生类构造函数的书写情况是不一样的,上面注释有代码。

没有虚基类的多继承,派生类构造函数的声明只包括其直接的基类

有虚基类的多继承,派生类构造函数的声明不仅包含其直接基类还要包含直接基类的虚基类。

2、

1)运行时,C创建对象时,先找到直接基类B1,调用直接基类B1的构造函数时,又调用A的构造函数,无基类,直接调用

2)之后,在调用B1的构造函数,之后再调B2的构造函数时,发现有基类A,但是A为虚基类,已经调用过一次,不再调用

3)之后,直接调用B2的构造函数,完了,就直接调用C的构造函数

说明:

1、虚基类怎么保证初始化派生类对象时,只被调用一次?

因为:初始化列表中要包括对 直接基类 + 虚基类 进行调用,但仅仅用建立对象的最远派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类

的构造函数的调用在执行中被忽略,即对其他基类来说,这个基类是虚假的,而不再调用虚基类,从而保证对虚基类子对象只初始化一次。

2、一个类可以在一个类族中既被用作虚基类,也被用作非虚基类。

转载于:https://www.cnblogs.com/tham/p/6827207.html

相关文章:

  • Set集合
  • Solr4.7从数据库导数据
  • 【转】 Key/Value之王Memcached初探:二、Memcached在.Net中的基本操作
  • hdu 2335 Containers
  • Druid Indexing 服务
  • iOS7中弹簧式列表的制作
  • python实现虚拟茶话会
  • IE6|IE7中li底部3px间距BUG
  • 移动前端开发之viewport的深入理解
  • 效率篇——AppleScript入门2
  • C# 利用socekt做到http监听,怎么样才能做到高性能
  • hadoop-2.2.0多个队列资源分配
  • 【安卓开发】Android系统中Parcelable和Serializable的区别
  • 《超能陆战队》人工智能离我们还有多远
  • NativeScript的开发体会
  • Android单元测试 - 几个重要问题
  • iOS动画编程-View动画[ 1 ] 基础View动画
  • java 多线程基础, 我觉得还是有必要看看的
  • Markdown 语法简单说明
  • PhantomJS 安装
  • python学习笔记 - ThreadLocal
  • RxJS: 简单入门
  • select2 取值 遍历 设置默认值
  • text-decoration与color属性
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 数据仓库的几种建模方法
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • (2015)JS ES6 必知的十个 特性
  • (Java)【深基9.例1】选举学生会
  • (JSP)EL——优化登录界面,获取对象,获取数据
  • (pt可视化)利用torch的make_grid进行张量可视化
  • (ros//EnvironmentVariables)ros环境变量
  • (附源码)计算机毕业设计ssm高校《大学语文》课程作业在线管理系统
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (完整代码)R语言中利用SVM-RFE机器学习算法筛选关键因子
  • .bat批处理(九):替换带有等号=的字符串的子串
  • .htaccess 强制https 单独排除某个目录
  • .NET Core 将实体类转换为 SQL(ORM 映射)
  • [ 隧道技术 ] cpolar 工具详解之将内网端口映射到公网
  • [ 云计算 | AWS 实践 ] 基于 Amazon S3 协议搭建个人云存储服务
  • [2023年]-hadoop面试真题(一)
  • [3D游戏开发实践] Cocos Cyberpunk 源码解读-高中低端机性能适配策略
  • [Android] 240204批量生成联系人,短信,通话记录的APK
  • [Android]如何调试Native memory crash issue
  • [Big Data - Kafka] kafka学习笔记:知识点整理
  • [Bzoj4722]由乃(线段树好题)(倍增处理模数小快速幂)
  • [Electron] 将应用打包成供Ubuntu、Debian平台下安装的deb包
  • [Hadoop in China 2011] Hadoop之上 中国移动“大云”系统解析
  • [HDU 3555] Bomb [数位DP]
  • [Invalid postback or callback argument]昨晚调试程序时出现的问题,MARK一下
  • [Java] 模拟Jdk 以及 CGLib 代理原理
  • [JavaScript] JavaScript事件注册,事件委托,冒泡,捕获,事件流
  • [javaSE] 数据结构(二叉查找树-插入节点)
  • [LeetCode] Verify Preorder Sequence in Binary Search Tree 验证二叉搜索树的先序序列
  • [NodeJS]NodeJS基于WebSocket的多用户点对点即时通讯聊天