普陀网站开发培训,wordpress页面播放器,兰州网站设计制作,图书馆建设网站注意点转载#xff1a;http://blog.csdn.net/pg_dog/article/details/70175488 今天呢#xff0c;我们来讲讲菱形继承与虚继承。这两者的讲解是分不开的#xff0c;要想深入了解菱形继承#xff0c;你是绕不开虚继承这一点的。它俩有着什么关系呢#xff1f;值得我们来剖析。 菱…转载http://blog.csdn.net/pg_dog/article/details/70175488 今天呢我们来讲讲菱形继承与虚继承。这两者的讲解是分不开的要想深入了解菱形继承你是绕不开虚继承这一点的。它俩有着什么关系呢值得我们来剖析。 菱形继承也叫钻石继承它是多继承的一种特殊实例吧它的基本架构如下图 在我们的设想中D所对应的对象模型应该如下图所示 下面我们来用一段代码验证一下
class A
{
public:A(){cout A() endl;}~A(){cout ~A() endl;}char a;
};class B :public A
{
public:B(){cout B() endl;}~B(){cout ~B() endl;}char b;
};class C :public A
{
public:C(){cout C() endl;}~C(){cout ~C() endl;}int c;
};
class D :public B, public C
{
public:D(){cout D() endl;}~D(){cout ~D() endl;}int d;};int main()
{cout sizeof(A) endl; //1cout sizeof(B) endl; //2cout sizeof(C) endl; //8cout sizeof(D) endl; //16system(pause);return 0;
}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768 上面显示的大小似乎证实了我们的猜想但实际上对象模型不是这样的如下图所示 但是你会发现这里面存在一个问题对象D中有两个‘a’存在数据冗余的问题如果对象BC中有两个同名的函数或同名成员变量本例中的变量‘a’那么对象D在调用该函数或该成员变量时该选择调用哪个呢这也就可以看出还存有二义性问题。那么该如何处理呢 解决二义性问题很简单你在调用函数时加上作用域运算符但是数据冗余问题还是没有解决。那么编译器是如何处理这两个问题的呢 为了解决二义性问题和数据冗余问题C引入了虚继承这一概念。下面重点来看虚继承。 虚继承 虚继承又称共享继承是面向对象编程的一种技术是指一个指定的基类在继承体系结构中将其成员数据实例共享给也从这个基类直接或间接派生的其他类。虚拟继承是多重继承中特有的概念虚拟继承就是为了解决多重继承而出现的。 这里我想引入《C Primer》这本书中对虚继承的有关描述。 在C语言中我们通过虚继承的机制来解决共享问题。虚继承的目的是令某个类作出声明承诺共享它的基类。其中共享的基类子对象称其为虚基类。在这种机制下不论虚基类在继承体系中出现了多少次在派生类中都只含有唯一一个共享的虚基类子对象。 这里还有一个概念虚基类。虚基类是通过virtual继承而来的派生类的基类。例如B虚继承了A所以A是B的虚基类而不是说A是虚基类。 看下图了解普通基类与虚基类的区别 按照上面的说法在对象D中应该只含有一个共享的虚基类子对象也就是例子中的_a。确实这样就解决了数据冗余与二义性问题。我们来验证上面的的说法。为了计算简单我将上例中每个类成员变量变为整形int 下面我们来看一段代码
class A
{
public:A(){cout A() endl;}~A(){cout ~A() endl;}void print(){printf(A);}int _a;
};class B :virtual public A //B虚继承A
{
public:B(){cout B() endl;}~B(){cout ~B() endl;}int _b;
};class C :virtual public A //C虚继承A
{
public:C(){cout C() endl;}~C(){cout ~C() endl;}int _c;
};
class D :public B, public C
{
public:D(){cout D() endl;}~D(){cout ~D() endl;}int _d;};int main()
{cout sizeof(A) endl;cout sizeof(B) endl;cout sizeof(C) endl;cout sizeof(D) endl;B bb;C cc;D dd;dd.B::_a 1;dd.C::_a 2;dd._b 3;dd._c 4;dd._d 5;system(pause);return 0;
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 B和C都是虚拟继承 按照我们之前的推理对象D的结构应该如图所示 我们来通过vs2013调试中的内存窗口来验证一下 看到这个结果是不是吓坏宝宝了和我们预测的完全不一样对象A和B中的_a跑到了最底部这种结构明显没有了数据冗余和二义性问题了这是怎么实现的呢这就要引入一新的概念——虚基类表。 虚基类表又称虚基表编译器会给虚继承而来的派生类生成一个指针vbptr指向一个虚基表而虚基表中存放的是偏移量。 我们来看对象D中的对象B它的第一部分第一行就是虚基类表指针vbptr它存的是虚基表的地址虚基表中存的是共享基类成员变量_a的相对此位置的偏移量我们来看看“01259b60”是个地址利用内存窗口我们可以发现里面存着两部分第一行“00 00 00 00”和第二行“00 00 00 14”虚基表中分两部分第一部分存储的是对象相对于存放vptr指针的偏移量在这就是“00 00 00 00”偏移量为0第二部分存储的是对象中基类对象部分相对于存放vbptr指针的地址的偏移量在这就是“00 00 00 14” 即20十六进制下14就是十进制的20也就是说偏移量是20个字节你可以用他们的地址相减验证一番。你可以看图数一下而对象D中的C的第一部分也是一样是个虚基表存的一样也是偏移量它存的地址“00369b68”里面是“00 00 00 0c”即十进制的12即偏移量为12字节。可以看下图 下面我再讲一个概念——虚函数这会在下篇文章多态中重点讲解但是这里有必要了解一下。 虚函数——类的成员函数前面加上virtual关键字则这个函数被称为虚函数。 虚函数用于定义类型特定行为的成员函数。通过引用和指针对虚函数的调用直到运行时才被解析依据是引用或指针所绑定对象的类型。《C Primer》中定义 虚函数重写覆盖当在子类定义了一个和父类完全相同的虚函数时则称这个这个子类的函数重写了覆盖了父类的虚函数。 既然说到这就有必要区分一下几个概念 重载在同一作用域内函数名相同参数不同返回值可不同的一对函数被称为重载。 隐藏重定义在不同作用域一般指基类和派生类函数名相同参数列表也相同但不需要virtual关键字的一组函数称为隐藏。 覆盖不在同一作用域一般指派生类和基类完全相同协变除外基类中函数必须有virtual关键字的一对函数被称为重定义。 注 1基类中定义了虚函数在派生类中该函数始终保持虚函数的特性。 2只有类的成员函数才能定义为虚函数。 3静态成员函数不能定义为虚函数。 4如果在类外定义虚函数只能在声明处加virtual关键字类外定义函数时不能加virtual关键字。 5构造函数不能为虚函数。 6最好不要将赋值运算符重载定义为虚函数因为使用容易混淆。 7不要在构造函数和析构函数调用虚函数在构造函数和析构函数中对象是不完整的可能会发生未定义的行为。 8最好将基类的析构函数定义为虚函数。注虽然基类的析构函数和派生类的析构函数名称不一样但构成覆盖因为编译器做了特殊处理 9虚继承只对虚继承子类后面派生出的子类有影响对虚继承自雷本身没有影响。 纯虚函数 纯虚函数——在成员函数的后面加上0则成员函数为纯虚函数。一个纯虚函数无需定义但也可以定义但是必须在类外也就是说我们不能在类内部为一个带有0的函数提供函数体。包含纯虚函数的类被称为抽象类也叫接口类。抽象类不能实例化出对象。他只是作为基类服务于派生类如果派生类不对基类的虚函数进行覆盖那他仍将是抽象基类。
class Father //抽象类接口类
{
public:virtual void fun() 0; //定义纯虚函数
protected:int _a;
};class Child
{
public:virtual void fun() 0; //覆盖否则Child也是抽象类接口类
}; 12345678910111213 继承和友元 友元关系不能继承也就是说基类友元不能访问子类私有和保护成员。 继承和静态成员 基类中定义了静态成员则整个继承体系中只有一个这样的成员。无论派生出多少的子类都只有一个静态成员实例。