多重继承中,一个基类可以在派生层次中出现多次,如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多分同名成员。C++提供虚基类的方法使得在继承间接共同基类时只保留一份成员。

  1.通常,每个类至初始化自己的直接基类,在应用于虚基类的时候,这个初始化策略会失败。如果使用这个规则,则可能多次初始化虚基类。为了解决这个问题,从具有虚基类的类继承的类初始化进行特殊处理。在虚派生中,由最底层的派生类构造函数初始化虚基类。即使是最底层派生类的非直接虚基类,也在最底层的虚派生类中调用其构造函数。

  2.无论虚基类出现在继承层次中的任何地方,总是在构造非虚基类之前构造虚基类。代码如下:

class A
{
public:
A()
{
cout<<"A constructor!"<<endl;
}
};

class B
{
public:
B()
{
cout<<"B constructor!"<<endl;
}
};

class C
{
public:
C()
{
cout<<"C constructor!"<<endl;
}
};

class D : public A, public B, virtual public C
{
public:
D()
{
cout<<"D constructor!"<<endl;
}
};

int _tmain(int argc, _TCHAR* argv[])
{
D d;
return 0;
}

运行结果:

可以看出,即使是C类在派生列表的最后,类C也是首先构造,因为C为虚基类。但是如果虚基类还有一个基类而且该基类不是虚基类,则必须先构造基类,代码如下:

class A
{
public:
  A()
  {
    cout<<"A constructor!"<<endl;
  }
};

class B
{
public:
  B()
  {
    cout<<"B constructor!"<<endl;
  }
};

class C
{
public:
  C()
  {
    cout<<"C constructor!"<<endl;
  }
};

class E : public C
{
public:
  E()
  {
    cout<<"E constructor!"<<endl;
  }
};

class D : public A, public B, virtual public E
{
public:
  D()
  {
    cout<<"D constructor!"<<endl;
  }
};

int _tmain(int argc, _TCHAR* argv[])
{
  D d;
  return 0;
}

运行结果:

虽然E是虚基类而C不是,但是还是先构造了C,因为C是E的基类,在构造一个类之前,必须先构造基类,即使是虚继承也不例外。

一、C++中的对象模型

1、 概念

语言中直接支持面向对象程序设计的部分;

对于各种支持的底层实现机制。(没看懂……)

2、 类中的成员分类

a) 成员函数

  i. static function

  ii. non static function

  iii. virtual function

b)  数据成员

  i. static member data

  ii. non static member data

3、 C++对象模型

a) 类对象内存布局中的包括

  i. non static member data

  ii. vptr(虚函数表指针)

  iii. vbptr(虚基类表指针)

b) 不包括

  i. static member data(存储在静态存储区)

  ii. 成员函数(存储在代码区)

c) virtual table

简称vtbl。存放着指针,这些指针指向该类每一个虚函数。虚表中的函数地址将按声明时的顺序排列。vtbl在类声明后就形成了,vptr是编译器生成的。

d) vptr的位置一般放在一个类对象的最前端。

e) 虚基类表

vbptr指向的表,用于存放虚继承中,虚基类存储相对于虚基类表指针的偏移量。

二、继承类型

1、普通继承(不包含虚函数)

a、单继承

class Base
{
public:
Base (int a = 1):base(a){}
void fun0(){cout << base << endl;}
int base;
};
class Derive:public Base
{
public:
Derive (int a = 2):derive(a){}
void fun1(){cout << base1 << endl;}
int derive;
};

b、多继承

class Base1
{
public:
Base1 (int a = 2):base1(a){}
void fun1(){cout << base1 << endl;}
int base1;
};
class Base2
{
public:
Base2 (int a = 3):base2(a){}
void fun2(){cout << base2 << endl;}
int base2;
};
class Derive: public Base1, public Base2
{
public:
Derive (int value = 4):derive (value){}
void fun3(){cout << derive << endl;}
int derive;
};

c、菱形继承

按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

注:菱形继承存在二义性问题,编译都不通过,只能通过指定特定基类的方式进行访问基类变量。

Derive d;

d.base =3; // 不正确

d.Base1::base = 3; // 正确

2、普通继承(包含虚函数)

  a、单继承(包含虚函数)

class Base
{
public:
Base (int a = 1):base(a){}
virtual void fun0(){cout << base << endl;}
int base;
};
class Derive:public Base
{
public:
Derive (int a = 2):derive(a){}
virtual void fun0(){};
virtual void fun1(){cout << derive << endl;}
int derive;
};

注:派生类中新增的虚函数追加到虚函数表后面。

b、多继承(包含虚函数)

class Base1
{
public:
Base1 (int a = 2):base1(a){}
virtual void fun1(){cout << base1 << endl;}
int base1;
};
class Base2
{
public:
Base2 (int a = 3):base2(a){}
virtual void fun2(){cout << base2 << endl;}
int base2;
};
class Derive: public Base1, public Base2
{
public:
Derive (int value = 4):derive (value){}
virtual void fun3(){cout << derive << endl;}
int derive;
};

注:派生类中新增的虚函数,追加到第一个基类的虚函数表的后面。

c、菱形继承(包含虚函数)

class Base
{
public:
Base (int a = 1):base(a){}
virtual void fun0(){cout << base << endl;}
int base;
};
class Base1:public Base
{
public:
Base1 (int a = 2):base1(a){}
virtual void fun1(){cout << base1 << endl;}
int base1;
};
class Base2:public Base
{
public:
Base2 (int a = 3):base2(a){}
virtual void fun2(){cout << base2 << endl;}
int base2;
};
class Derive: public Base1, public Base2
{
public:
Derive (int value = 4):derive (value){}
virtual void fun3(){cout << derive << endl;}
int derive;
};

注:分析时,由上到下依次分析。存在二义性问题和内存冗余问题。

 3、虚继承(不包含虚函数)

新增虚基类指针,指向虚基类表,虚基类表中首项存储虚基类指针的偏移量,接下来依次存储虚基类的偏移量(偏移量是相对于虚基类表指针的存储地址)。

   a、单虚继承(不包含虚函数)

class Base
{
public:
Base (int a = 1):base(a){}
void fun0(){cout << base << endl;}
int base;
};
class Base1:virtual public Base
{
public:
Base1 (int a = 2):base1(a){}
void fun1(){cout << base1 << endl;}
int base1;
};

   b、多虚继承(不包含虚函数)

class Base1
{
public:
Base1 (int a = 2):base1(a){}
void fun1(){cout << base1 << endl;}
int base1;
};
class Base2
{
public:
Base2 (int a = 3):base2(a){}
void fun2(){cout << base2 << endl;}
int base2;
};
class Derive:virtual public Base1, virtual public Base2
{
public:
Derive (int value = 4):derive (value){}
void fun3(){cout << derive << endl;}
int derive;
};

  c、菱形虚继承(不包含虚函数)

   第一种形式:

class Base
{
public:
Base (int a = 1):base(a){}
void fun0(){cout << base << endl;}
int base;
};
class Base1:virtual Base
{
public:
Base1 (int a = 2):base1(a){}
void fun1(){cout << base1 << endl;}
int base1;
};
class Base2:virtual Base
{
public:
Base2 (int a = 3):base2(a){}
void fun2(){cout << base2 << endl;}
int base2;
};
class Derive:virtual public Base1, virtual public Base2
{
public:
Derive (int value = 4):derive (value){}
void fun3(){cout << derive << endl;}
int derive;
};

注:分析派生类的内存分布时,也是由上到下分析。虚继承将基类置于内存末尾,但是置于末尾的顺序也有一定的次序。首先Base先放到末尾,然后Base1放到末尾,最后Base2放到末尾。

第二种形式:

class Base
{
public:
Base (int a = 1):base(a){}
void fun0(){cout << base << endl;}
int base;
};
class Base1:virtual public Base
{
public:
Base1 (int a = 2):base1(a){}
void fun1(){cout << base1 << endl;}
int base1;
}; class Base2:virtual public Base
{
public:
Base2 (int a = 3):base2(a){}
void fun2(){cout << base2 << endl;}
int base2;
};
class Derive: public Base1, public Base2
{
public:
Derive (int value = 4):derive (value){}
void fun3(){cout << derive << endl;}
int derive;
};

注:分析的原则,从上到下,依次分析。

4、 虚继承(包含虚函数)

a、单虚继承(包含虚函数)

class Base
{
public:
Base (int a = 1):base(a){}
virtual void fun0(){cout << base << endl;}
int base;
};
class Base1:virtual Base
{
public:
Base1 (int a = 2):base1(a){}
virtual void fun1(){cout << base1 << endl;}
int base1;
};

  与普通的包含虚函数的单继承相比,派生类拥有自己的虚函数表以及虚函数表指针,而不是与基类共用一个虚函数表。注意虚函数表指针和虚基类表指针的存储顺序。

  b、多虚继承(包含虚函数)

class Base1
{
public:
Base1 (int a = 2):base1(a){}
virtual void fun1(){cout << base1 << endl;}
int base1;
}; class Base2
{
public:
Base2 (int a = 3):base2(a){}
virtual void fun2(){cout << base2 << endl;}
int base2;
};
class Derive:virtual public Base1, virtual public Base2
{
public:
Derive (int value = 4):derive (value){}
virtual void fun3(){cout << derive << endl;}
int derive;
};

c、菱形虚继承(包含虚函数)

第一种形式:

class Base
{
public:
Base (int a = 1):base(a){}
virtual void fun0(){cout << base << endl;}
int base;
};
class Base1:virtual public Base
{
public:
Base1 (int a = 2):base1(a){}
virtual void fun1(){cout << base1 << endl;}
int base1;
}; class Base2:virtual public Base
{
public:
Base2 (int a = 3):base2(a){}
virtual void fun2(){cout << base2 << endl;}
int base2;
};
class Derive: public Base1, public Base2
{
public:
Derive (int value = 4):derive (value){}
virtual void fun3(){cout << derive << endl;}
int derive;
};

第二种形式:

class Base
{
public:
Base (int a = 1):base(a){}
virtual void fun0(){cout << base << endl;}
int base;
};
class Base1:virtual public Base
{
public:
Base1 (int a = 2):base1(a){}
virtual void fun1(){cout << base1 << endl;}
int base1;
}; class Base2:virtual public Base
{
public:
Base2 (int a = 3):base2(a){}
virtual void fun2(){cout << base2 << endl;}
int base2;
};
class Derive: virtual public Base1,virtual public Base2
{
public:
Derive (int value = 4):derive (value){}
virtual void fun3(){cout << derive << endl;}
int derive;
};

自行脑补C++类对象的内存结构……

注:上述虚函数中,如果派生类重写了基类的虚函数,则对应虚函数表中的虚函数应该修改成重新后的虚函数,即Base::fun()->Derive::fun()。

c++继承汇总(单继承、多继承、虚继承、菱形继承)的更多相关文章

  1. Unity 游戏框架搭建 (十三) 无需继承的单例的模板

    之前的文章中介绍的Unity 游戏框架搭建 (二) 单例的模板和Unity 游戏框架搭建 (三) MonoBehaviour单例的模板有一些问题. 存在的问题: 只要继承了单例的模板就无法再继承其他的 ...

  2. 【整理】C++虚函数及其继承、虚继承类大小

    参考文章: http://blog.chinaunix.net/uid-25132162-id-1564955.html http://blog.csdn.net/haoel/article/deta ...

  3. C++中的类继承(4)继承种类之单继承&多继承&菱形继承

    单继承是一般的单一继承,一个子类只 有一个直接父类时称这个继承关系为单继承.这种关系比较简单是一对一的关系: 多继承是指 一个子类有两个或以上直接父类时称这个继承关系为多继承.这种继承方式使一个子类可 ...

  4. C++中的类继承之单继承&多继承&菱形继承

     C++中的类继承之单继承&多继承&菱形继承 单继承是一般的单一继承,一个子类只 有一个直接父类时称这个继承关系为单继承.这种关系比较简单是一对一的关系: 多继承是指 一个子类有两个或 ...

  5. inheritance,菱形继承, 虚继承,virtual

    //菱形继承   |||||||   虚继承 #include <iostream> using namespace std; class R {     int r; public:   ...

  6. C++类有继承时,析构函数必须为虚函数

    C++类有继承时,析构函数必须为虚函数.如果不是虚函数,则使用时可能存在内在泄漏的问题. 假设我们有这样一种继承关系: 如果我们以这种方式创建对象: SubClass* pObj = new SubC ...

  7. C++中对C的扩展学习新增内容———面向对象(继承)函数扩展性及虚函数机制

    1.c语言中的多态,动态绑定和静态绑定 void do_speak(void(*speak)()) { speak(); } void pig_speak() { cout << &quo ...

  8. day18-Python运维开发基础(单继承 / 多继承 / 菱形继承、类的多态)

    1. 单继承 / 多继承 / 菱形继承 # ### 继承 : 一个类除了自身所拥有的属性方法之外,还获取了另外一个类的成员属性和方法 """ 一个类可以继承另外一个类,那 ...

  9. day23:单继承&多继承&菱形继承&__init__魔术方法

    1.单继承 1.1 关于继承的一些基本概念 1.2 子类可以调用父类的公有成员 1.3 子类无法调用父类的私有成员 1.4 子类可以改写父类的方法 2.多继承 2.1 多继承的基本语法 2.2 sup ...

随机推荐

  1. webdriver.close() quit() 批量kill进程 内存耗尽的解决办法

    问题现象: shell窗口卡,换IP的登录窗,不开: 猜测: 内存耗尽 spider_url,py driver = webdriver.PhantomJS( executable_path='/us ...

  2. Delphi如何实现多国语言

    Delphi里的多语言处理方法都一样, 都是通过资源DLL的形式进行加载处理. Delphi在加载form数据的时候会判断当前的系统语言,然后根据语言加载不同的资源dll, 来实现多国语言的功能. 下 ...

  3. springboot对传参的拦截统一处理

    在学习某网<java秒杀系统方案优化>的课程中,学到了一种springboot对传参的拦截统一处理的方式,特记录一下. 如后台方法一般需要根据token从Session中获取User对象, ...

  4. 配置webpack中externals来减少打包后vendor.js的体积

    在日常的项目开发中,我们会用到各种第三方库来提高效率,但随之带来的问题就是打包后的vendor.js体积过大,导致加载时空白页时间过长,给用户的体验太差.为此我们需要减少vendor.js的体积,从本 ...

  5. LoadRunner使用动态链接库技术

    什么是动态库? 动态库一般又叫动态链接库英文为DLL,是Dynamic Link Library 的缩写形式,DLL是一个包含可由多个程序同时使用的代码和数据的库,DLL不是可执行文件.动态链接提供了 ...

  6. Swift语言学习(四)字符串与Array等集合的使用

    Swift 提供了C和Objective-C基础类型的自己一套版本,包括用于整型的Int,用于浮点值的Double和Float,用于布尔值的Boolean,和用于文本数据的String.Swift还提 ...

  7. Asp与Asp.Net

    1.Asp是什么? ASP是动态服务器页面(Active Server Page)的英文缩写.是微软公司开发的代替CGI脚本程序的一种应用,它可以与数据库和其它程序进行交互,是一种简单.方便的编程工具 ...

  8. UVA-11549(floyd判圈算法)

    题意: 给一个整数k,每次平方后只能显示结果的前n位,问在这个过程中能得到的最大的数是多少; 思路: floyd判圈算法;它的正确性建立在这得到的这些数是有限的,所以一定是一个循环,在这个循环的圈里面 ...

  9. Android 不同阶段 Logo 显示

    /********************************************************************* * Android 不同阶段 Logo 显示 * 说明: ...

  10. Ubuntu:Could not get lock /var/lib/dpkg/lock

    在ubuntu上使用apt-get时,碰过如下问题: 看意思是上一次使用apt-get时异常退出了,锁住了,google了下解决方案如下: 1.先判断是否有apt-get进程在跑,同一时刻只能有一个a ...