C++学习从零开始(二)


  由于篇幅 制约,本篇为《C++从零开始(十一)》的中篇, 注明多重继承、虚继承和虚函数的实现 模式 。

  多重继承

  这里有个 乏味的问题,如下:

  struct A { long a, b, c; char d; }; struct B : public A { long e, f; };

  上面的B::e和B::f映射的偏移是多少?不同的编译器有不同的映射 后果,关于派生的实现,C++并没有强行规定 。大多数编译器都是让B::e映射的偏移值为16(即A的长度,关于自定义类型的长度可参考《C++从零开始(九)》),B::f映射20 。这相当于先把空间留出来罗列父类的成员变量,再罗列自己的成员变量 。然而存在这样的语义--西红柿即是蔬菜又是水果,鲸鱼即是海洋生物又是脯乳动物 。即一个实例既是这 品种型又是那 品种型,关于此,C++提供了多重派生或称多重继承,用“,” 间隔各父类,如下:

  struct A { long A_a, A_b, c; void ABC(); }; struct B { long c, B_b, B_a; void ABC

  (); };

  struct AB : public A, public B { long ab, c; void ABCD(); };

  void A::ABC() { A_a = A_b = 10; c = 20; }

  void B::ABC() { B_a = B_b = 20; c = 10; }

  void AB::ABCD() { A_a = B_a = 1; A_b = B_b = 2; c = A::c = B::c = 3; }

  void main() { AB ab; ab.A_a = 3; ab.B_b = 4; ab.ABC(); }

  上面的 构造AB从 构造A和 构造B派生而来,即我们 可以说ab既是A的实例也是B的实例,而且还是AB的实例 。那么在派生AB时,将生成几个映射元素?照前篇的说法,除了AB的类型定义符“{}”中定义的AB::ab和AB::c以外(类型均为long AB::),还要生成继承

  来的映射元素,各映射元素名字的 润饰换成AB::,类型不变,映射的值也不变 。 因此关于两个父类,则生成8个映射元素(每个类都有4个映射元素), 比方其中一个的名字为AB::A_b,类型为long A::,映射的值为4;也有一个名字为AB::B_b,类型为long B::,映射的值依然为4 。 留神A::ABC和B::ABC的名字一样, 因此其中两个映射元素的名字都为AB::ABC,但类型则一个为void( A:: )()一个为void( B:: )(),映射的地址分别为A::ABC和B::ABC 。同样,就有三个映射元素的名字都为AB::c,类型则分别为long A::、long B::和long AB::,映射的偏移值 顺次为8、0和28 。照前面说的先罗列父类的成员变量再罗列子类的成员变量, 因此类型为long AB::的AB::c映射的值为两个父类的长度之和再外加AB::ab所带来的偏移 。 留神问题,上面继承生成的8个映射元素中有两对同名,但不存在任何问题,由于它们的类型不同,而最终编译器将依据它们各自的类型而 批改它们的名字以 构成符号,这样衔接时将不会 产生重定义问题,但带来 其余问题 。ab.ABC(); 定然是ab.AB::ABC();的简写,由于ab是AB类型的,但现在由于有两个AB::ABC, 因此上面直接书写ab.ABC将报错,由于 无奈晓得是要哪个AB::ABC,这时怎么办?

   回顾本文上篇提到的公共、 掩护、私有继承,其中说过,公共就 示意外界 可以将子类的实例当作父类的实例来 对待 。即全部需求用到父类实例的地方,假如是子类实例,且它们中间是公共继承的关系,则编译器将会进行隐式类型转换将子类实例转换成父类实例 。 因此上面的ab.A_a = 3;实际是ab.AB::A_a = 3;,而AB::A_a的类型是long A::,而成员操作符要求两边所属的类型 雷同,左边类型为AB,且AB为A的子类, 因此编译器将自动进行隐式类型转换,将AB的实例变成A的实例, 而后再计算成员操作符 。

   留神前面说AB::A_b和AB::B_b的偏移值都为4,则ab.A_b = 3;岂不是等效于ab.B_b = 3;? 即便依照上面的说法,由于AB::A_b和AB::B_b的类型分别是long A::和long B::,也最多只不过前者转换成A的实例后者转换成B的实例,AB::A_b和AB::B_b映射的偏移依然没变啊 。 因此变的是成员操作符左边的数字 。关于 构造AB, 假如先罗列父类A的成员变量再罗列父类B的成员变量,则AB::B_b映射的偏移就应该为16( 构造A的长度外加B::c引入的偏移),但它实际映射为4, 因此就将成员操作符左侧的地址类型的数字外加12( 构造A的长度) 。而关于AB::A_b,由于 构造A的成员变量先被罗列,故只偏移0 。 假如上面ab对应的地址为3000,关于ab.B_b = 4;,AB类型的地址类型的数字3000在“.”的左侧,转成B类型的地址类型的数字3012(由于偏移12), 而后再将“.”右侧的偏移类型的数字4外加3012,最终返回类型为long的地址类型的数字3016,再 接续计算“=” 。同样也可晓得ab.A_a = 3;中的成员操作符最终返回long类型的地址类型的数字3000,而ab.A_b将返回3004,ab.ab将返回3024 。