转载来自:CSDN
insistGoGo  (http://blog.csdn.net/insistgogo)


多继承的定义:派生类的基类大于一个

语法:

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

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

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

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

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

构造函数语法:

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

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

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

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

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

(2)子对象的构造函数

(3)派生类的构造函数

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

注意:

1)析构函数能继承;

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

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

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

举例:

  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、继承/多重继承一般是公有继承,保护继承/私有继承只是在技术讨论较多,实际使用较少。

多继承中同名覆盖原则

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

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

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

举例:

  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. }

多继承带来的二义性:

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

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

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

虚基类:

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

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

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

语法:

  1. class 派生类名:virtual 继承方式 类名  //在派生类定义的时候写。
  2. {
  3. }

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

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

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

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

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

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

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

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

举例:

  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的构造函数

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

错误代码:

  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. void display()
  35. {
  36. cout<<"a="<<a<<endl;//使用A::a区分也不行,这里的a是从A得到的,B1和B2都继承到了,到D中就有两份拷贝,都来自A产生歧义
  37. }
  38. };
  39. void main()
  40. {
  41. D d(1);
  42. d.display();
  43. system("pause");
  44. }

基类的层次图:

正确代码:

  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. void display()
  35. {
  36. cout<<"a="<<a<<endl;
  37. //使用虚基类时,A只执行一次,调用B的时候调用一次虚基类,调用C时,就没有调用A了,D中的a也只有一个拷贝,因而不产生歧义
  38. }
  39. };
  40. void main()
  41. {
  42. D d(1);
  43. d.display();
  44. system("pause");
  45. }

使用虚基类后的层次图:

注意:

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

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

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

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

2、

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

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

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

说明:

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

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

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

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

C++ 多继承与虚基类的更多相关文章

  1. YTU 2622: B 虚拟继承(虚基类)-沙发床(改错题)

    2622: B 虚拟继承(虚基类)-沙发床(改错题) 时间限制: 1 Sec  内存限制: 128 MB 提交: 487  解决: 393 题目描述 有一种特殊的床,既能当床(Bed)用又能当沙发(S ...

  2. 【C++】继承(虚基类)

    类的继承与派生 面向对象技术强调软件的可重用性,这种重用性通过继承机制来实现.而在类的继承过程中,被重用的原有类称为基类,新创建的类称为派生类.派生类定义语法格式如下: class <派生类名& ...

  3. C++ (P160—)多继承 二义性 虚基类 “向上转型”

    1 多继承中,必须给每个基类指定一种派生类型,如果缺省,相应的基类则取私有派生类型,而不是和前一个基类取相同的派生类型 2 一个类的保护成员只能被本类的成员函数或者它的派生类成员函数访问 3 由于c+ ...

  4. 【M24】了解虚方法、多继承、虚基类、RTTI的成本

    1.编译器必须实现出C++语言的特性.一般情况下,我们只需要使用这些特性就好了,不需要关心内部的实现细节.但是,有些特性的实现,会对对象的大小和成员方法的执行速度造成影响.因此,有必要了解内部实现的细 ...

  5. C/C++ 多继承{虚基类,虚继承,构造顺序,析构顺序}

    C/C++:一个基类继承和多个基类继承的区别 1.对多个基类继承会出现类之间嵌套时出现的同名问题,如果同名变量或者函数出现不在同一层次,则底层派生隐藏外层比如继承基类的同名变量和函数,不会出现二义性, ...

  6. C++ 虚继承实现原理(虚基类表指针与虚基类表)

    虚继承和虚函数是完全无相关的两个概念. 虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝.这将存在两个问题:其一,浪费存储空间:第二,存在二义性问题,通常可 ...

  7. C++虚基类详解(转)

    我们知道,如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员.在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避 ...

  8. c++ 虚基类应用

    多重继承存在二义性,为了消除二义性在访问相同名称的属性时需要加上类名,加以区分.虽然这样可以解决二义性,但是相同的属性出现在多个基类中,为了解决数据冗余,c++引入了虚基类. 虚基类定义:class ...

  9. C++ 虚基类的定义、功能、规定

    原文声明:http://blog.sina.com.cn/s/blog_93b45b0f01011pkz.html 虚继承和虚基类的定义是非常的简单的,同时也是非常容易判断一个继承是否是虚继承的,虽然 ...

随机推荐

  1. python创建系统用户和用户组

    #coding=utf8 import pwd import grp import sys from _utils.patrol2 import run_cmd info=None try: info ...

  2. 高级 Java 必须突破的 10 个知识点!

    1.Java基础技术体系.JVM内存分配.垃圾回收.类装载机制.性能优化.反射机制.多线程.网络编程.常用数据结构和相关算法. 2.对面向对象的软件开发思想有清晰的认识.熟悉掌握常用的设计模式. 3. ...

  3. centos7的安装主要步骤选择

    选择语言,选择英语 选择时区done确认选择 安全策略,选择默认 安装源文件 软件包选择,此处选择 最小安装 选择磁盘,并分区

  4. 自定义el标签

    编写java代码 package com.ycjk.common; public class FormatJSEltarg { public static String format(String s ...

  5. 图解修改Maven本地仓库存储路径

    1 从Maven中心仓库下载到本地的jar包的默认存放在"${user.home}/.m2/repository"中,${user.home}表示当前登录系统的用户目录(如&quo ...

  6. Ex 6_9 某个字符串处理语言提供了一个将字符串一分为二的基本操作..._第六次作业

    设字符串的长度为n,整型数组arr[0. . .n-1]的第一个数和最后一个数为开始点与结束点的位置,中间的数为拆分点的位置,设cost[i,j]为第i个分割点到第j个分割点的最小代价,两个分割点之间 ...

  7. SonarQube代码质量管理工具的升级(sonarqube6.2 + sonar-scanner-2.8 + MySQL5.6+)

    SonarQube升级注意事项 0. 前提条件 如果之前是使用sonarqube5.2 + sonar-runner-2.4 +MySQL5.5版本或者类似的组合. 安装方法请参照SonarQube代 ...

  8. (二)使用CXF开发WebService服务器端接口

    CXF作为java领域主流的WebService实现框架,Java程序员有必要掌握它. CXF主页:http://cxf.apache.org/ 简介:百度百科 今天的话,主要是用CXF来开发下Web ...

  9. (一)什么是webservice?

    第一节: 第一节:Webservice 简介 第二节: 第二节:CXF 简介 webservice 有的人一看到这个,估计会认为这个是一种新技术,一种新框架. 其实不是,严格的说,webservice ...

  10. String:(字符串)中常用的方法

    package stringyiwen; //字符串中常用的方法public class StringTest03 { public static void main(String[] args) { ...