图文例解C++类的多重继承与虚拟继承

上传人:ji****72 文档编号:37682877 上传时间:2018-04-20 格式:DOCX 页数:18 大小:106.30KB
返回 下载 相关 举报
图文例解C++类的多重继承与虚拟继承_第1页
第1页 / 共18页
图文例解C++类的多重继承与虚拟继承_第2页
第2页 / 共18页
图文例解C++类的多重继承与虚拟继承_第3页
第3页 / 共18页
图文例解C++类的多重继承与虚拟继承_第4页
第4页 / 共18页
图文例解C++类的多重继承与虚拟继承_第5页
第5页 / 共18页
点击查看更多>>
资源描述

《图文例解C++类的多重继承与虚拟继承》由会员分享,可在线阅读,更多相关《图文例解C++类的多重继承与虚拟继承(18页珍藏版)》请在金锄头文库上搜索。

1、图文例解图文例解 C+类的多重继承与虚拟继承类的多重继承与虚拟继承在过去的学习中,我们始终接触的单个类的继承,但是在现实生活中,一些新事物往往会拥有两个或者两个以上事物的属性,为了解决这个问题,C+引入了多重继承的概念,C+C+允许为一允许为一个派生类指定多个基类,这样的继承结构被称做多重继承个派生类指定多个基类,这样的继承结构被称做多重继承。举个例子,交通工具类可以派生出汽车和船连个子类,但拥有汽车和船共同特性水陆两用汽车就必须继承来自汽车类与船类的共同属性。 由此我们不难想出如下的图例与代码:当一个派生类要使用多重继承的时候,必须在派生类名和冒号之后列出所有基类的类名,并用逗好分隔。/程序

2、作者:管宁 /站点:dev- /所有稿件均有版权,如要转载,请务必著名出处和作者 #include using namespace std; class Vehicle public: Vehicle(int weight = 0) Vehicle:weight = weight; void SetWeight(int weight) cout using namespace std; class Vehicle public: Vehicle(int weight = 0) Vehicle:weight = weight; coutclass IBase4 5 6 7 8 9 10 11 1

3、2 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public:IBase() : n(0) virtual IBase() void show() printf(“%dn“, n); int inc() return +n; int dec() return -n; protected:int n; ;class IA : public IBase public:virtual IA() ;class IB : public IBase publi

4、c:virtual IB() ;class CImpl : public IA, public IB public:virtual CImpl() ;int main(int argc, char* argv) CImpl o;IA *pA = IB *pB = pA-inc();pA-show();pB-dec();pB-show();45 46 47return 0; 编译,OK,成功了!好,运行试一试。run-result-1:为什么是 1 和-1 呢?明明 n 只在继承的一个类 IBase 里面有,一次加 1,一次减一,结果不是应该是 1 和0 么?是不是很奇怪?这便是使用多重继承的时

5、候经常产生的第一个问题:多副本的数据存储。这便是使用多重继承的时候经常产生的第一个问题:多副本的数据存储。当然这个问题很好解决,只需要使用虚继承即可解决。只需要在 IA 和 IB 的定义中,在 public IBase 前加入virtual 关键字即可。? 1 2class IA : virtual public IBase class IB : virtual public IBase现在再让我们来看一看运行结果:run-result-2:结果已经正确了,为什么会发生这种情况呢?虚继承到底干了些什么呢?我们先来看看在没有使用虚继承的情况下,CImpl 的在内存中是怎么样的:cimpl-mem

6、ory-normal:对于普通的 public 继承(非虚继承),C+会将基类和派生类的内容放在一块,合起来组成一个完整的派生类,在内存上看,它们是连在一起的。按照这样的规则,在这里,IA 和 IB 中就会各包含一个 IBase,而IA,IB 和 CImpl 中的部分内容又共同组成了 CImpl。在将 CImpl 对象 o 的指针转型成 IA 的指针 pA 过程中,指针将被移动到 IA 所在的部分区域,同样在转型成 IB 的过程中,指针将被移动到 IB 所在的部分区域(也就是说,转型之后,指针的值都不一样)。在之后的操作中,pA 操作的便是 IA 这个部分中的 IBase,而 pB 操作的便是

7、 IB 这个部分中的 IBase,最后 IA 部分中的 IBase 变成了 1,而 IB 部分中的 IBase 变成了-1。所以输出的结果也就变成了 1 和-1 了。之后我们修改成了虚继承,看看到底发生了什么?cimpl-memory-virtual:原来的 IA 和 IB 中的 IBase 部分变成了一个指向基类的指针,而基类也变成了一个单独的部分。这样一旦对基类做任何的修改,都会通过这个指针重定向到这个独立的基类上去,于是,就不存在多副本的数据存储了,这个诡异的问题也就解决了。但是当然从这个图上我们也可以看到,使用虚继承后,访问数据多了一次跳转,这多出的一次跳转将导致效率的下降一倍甚至更多

8、,所以如果一个类使用的非常频繁,很明显应该尽量避免使用虚继承。 二义性二义性当然数据的存储只是使用多重继承中遇到的一个问题,现在我们来看另外一个问题,函数的二义性。首先我们先把数据的存储抛开,单纯的来看一个只有函数的继承关系。? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17class IBase public:virtual IBase() void foo() ;class IA : public IBase public:virtual IA() ;class IB : public IBase public:virtual IB() 18 19 20

9、 21 22 23 24 25 26 27 28 29 30 31 32;class CImpl : public IA, public IB public:virtual CImpl() ;int main(int argc, char* argv) CImpl o;o.foo(); / 直接调用 CImpl 的 foo 函数return 0; 编译一下,试试。error C2385: ambiguous access of foocould be the foo in base IBaseor could be the foo in base IBaseerror C3861: foo:

10、identifier not found出错了!杯具。为什么?错误还这么奇怪,神马叫做可以是 IBase 中的 foo 又可以是 IBase 中的 foo 呢?这就是使用多重继承的时候经常产生的第二个问题:二义性。这就是使用多重继承的时候经常产生的第二个问题:二义性。在使用多重继承时,如果有两个被继承的类拥有共同的基类,那么就很容易出现这种情况。那什么是二义性呢?我们先来看一个更简单的继承关系:? 1 2 3 4 5 6 7 8 9 10 11 12 13class A public:void foo(); ;class B : public A public:void foo(); ;cla

11、ss C : public B14 15 16 17 public:void foo(); ;我们可以把继承关系中,两个类之间沿着基类方向的相隔的继承级数看成一个距离,那么 C 到 A 的距离是2,B 到 A 的距离就是 1。当然距离不能为负。当我们对 ABC 中某个对象调用 foo 函数的时候,编译器会优先选择离当前指针类型的距离最短的一个函数实现去调用,也就是说,foo 函数的查找路径是 C-B-A,找到一个最近的去调用。而对于我们当前这个继承关系来说,IA 和 IB 还是各包含一份 IBase 的实例,虽然在内存里这里仅仅是包含一份数据,但是在编译的过程中,IA 和 IB 中还包含了一份

12、从 IBase 中继承下来的函数列表。所以有两个包所以有两个包含有含有 foo 函数类与函数类与 CImpl 类的距离是一样的,所以在对类的距离是一样的,所以在对 CImpl 调用调用 foo 函数,就产生了所谓的二义性,函数,就产生了所谓的二义性,除非我们指定使用除非我们指定使用 IA:foo 或者或者 IB:foo,否则编译器将无法决定使用哪一个基类的,否则编译器将无法决定使用哪一个基类的 foo 函数。函数。? 1o.IA:foo(); / 指定调用 CImpl 从 IA 部分继承过来的 foo 函数,这样就可以编译通过了。当然如果我们这样写代码也是不行的:?1 2IBase *pBas

13、e = / 指针转义时的二义性,不知道是使用 IA 中的 IBase 部分,还是 IB 中的 IBase 部 分 o.inc(); / 数据访问时的二义性,不知道是访问 IA 中 IBase 部分的 n,还是 IB 中 IBase 部分的 n 多重继承中的虚函数多重继承中的虚函数既然直接使用多重继承会有如此多的问题,那么我们能不能通过虚函数来解决这个问题呢?这里小小的提一下,刚刚二义性里面说到两个类的距离,对于编译器来说,一般是找离当前的类距离最近的函数实现来调用(或者数据来访问),而虚函数则是让编译器做相反的事情:找一个离当前类反向距离最远的函数实现来调用。好,我们先把上面的程序做一点点小改

14、变,把 foo()函数变成一个虚函数,看看有什么变化。? 1 2 3 4 5 6class IBase public:virtual IBase() virtual void foo() / 变成虚函数了 ;编译,结果还是失败。error C2385: ambiguous access of foocould be the foo in base IBaseor could be the foo in base IBaseerror C3861: foo: identifier not found产生问题的原因依然是二义性。即便换成 virtual 函数,也不能改变二义性这个问题。为什么呢?因

15、为我们是用的.运算符来访问的,而不是用指针,所以这里虚函数和普通函数没有任何区别。=.=。好,我们再来小小的修改一下,把他变成指针,让他通过虚表去访问,看看行不行。? 1 2CImpl *p = p-foo();编译,结果。还是一样失败。好吧,我们可以把调用 foo()的几句话都去掉,来看看 CImpl 中生成的虚表到底是个什么样子。debug-result-vptr-1:在这个实例中,IA 和 CImpl 部分公用一个虚表,而 IB 则使用另外的一个虚表(两个虚表这个特性主要是在指针类型转换的时候有用,这里就不说了)。在这 IA 的虚表中存在一个指向 IBase:foo()的指针,在 IB 的虚表中也存在一个指向 IBase:foo()的指针,所以在 CImpl 中,可以找到两个 IBase:foo()函数的指针,这样,编译器就无法确定到底应该使用哪一个IBase:foo()函数作为他自己的 foo()函数了。二义性也就产生了。既然如此,那解决起来就没有什么别的办法了,只能把 foo 函数的最终在 CImpl 中实现一次了。? 1 2 3 4 5 6 7 8 9class CImpl : public IA, public IB public:virtual CImpl() virtual

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 行业资料 > 其它行业文档

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号