当前位置导航:炫浪网>>网络学院>>编程开发>>C++教程>>C++进阶与实例

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.

    同样,这样也将进行隐式类型转换long AB::*p = &AB::B_b;。注意AB::B_b的类型为long B::,则将进行隐式类型转换。如何转换?原来AB::B_b映射的偏移为4,则现在将变成12+4=16,这样才能正确执行ab.*p = 10;。

    这时再回过来想刚才提的问题,AB::ABC无法区别,怎么办?注意还有映射元素A::ABC和B::ABC(两个AB::ABC就是由于它们两个而导致的),因此可以书写ab.A::ABC();来表示调用的是映射到A::ABC的函数。这里的A::ABC的类型是void( A:: )(),而ab是AB,因此将隐式类型转换,则上面没有任何语法问题(虽然说A::ABC不是结构AB的成员,但它是AB的父类的成员,C++允许这种情况,也就是说A::ABC的名字也作为类型匹配的一部分而被使用。如假设结构C也从A派生,则有C::a,但就不能书写ab.C::a,因为从C::a的名字可以知道它并不属于结构AB)。同样ab.B::ABC();将调用B::ABC.注意上面结构A、B和AB都有一个成员变量名字为c且类型为long,那么ab.c = 10;是否会如前面ab.ABC();一样报错?不会,因为有三个AB::c,其中有一个类型和ab的类型匹配,其映射的偏移为28,因此ab.c将会返回3028.而如果期望运用其它两个AB::c的映射,则如上通过书写ab.A::c和ab.B::c来偏移ab的地址以实现。

    注意由于上面的说法,也就可以这样:void( AB::*pABC )() = B::ABC; ( ab.*pABC )();。这里的B::ABC的类型为void( B:: )(),和pABC不匹配,但正好B是AB的父类,因此将进行隐式类型转换。如何转换?因为B::ABC映射的是地址,而隐式类型转换要保证在调用B::ABC之前,先将this的类型变成B*,因此要将其加12以从AB*转变成B*.由于需要加这个12,但B::ABC又不是映射的偏移值,因此pABC实际将映射两个数字,一个是B::ABC对应的地址,一个是偏移值12,结果pABC这个指针的长度就不再如之前所说的为4个字节,而变成了8个字节(多出来的4个字节用于记录偏移值)。

    还应注意前面在AB::ABCD中直接书写的A_b、c、A::c等,它们实际都应该在前面加上this->,即A_b = B_b = 2;实际为this->A_b = this->B_b = 2;,则同样如上,this被偏移了两次以获得正确的地址。注意上面提到的隐式类型转换之所以会进行,是因为继承时的权限满足要求,否则将失败。即如果上面AB保护继承A而私有继承B,则只有在AB的成员函数中可以如上进行转换,在AB的子类的成员函数中将只能使用A的成员而不能使用B的成员,因为权限受到限制。如下将失败。

    struct AB : protected A, private B {…};

    struct C : public AB { void ABCD(); };

    void C::ABCD() { A_b = 10; B_b = 2; c = A::c = B::c = 24; }

    这里在C::ABCD中的B_b = 2;和B::c = 24;将报错,因为这里是AB的子类,而AB私有继承自B,其子类无权将它看作B.但只是不会进行隐式类型转换罢了,依旧可以通过显示类型转换来实现。而main函数中的ab.A_a = 3; ab.B_b = 4; ab.A::ABC();都将报错,因为这是在外界发起的调用,没有权限,不会自动进行隐式类型转换。

    注意这里C::ABCD和AB::ABCD同名,按照上面所说,子类的成员变量都可以和父类的成员变量同名(上面AB::c和A::c及B::c同名),成员函数就更没有问题。只用和前面一样,按照上面所说进行类型匹配检验即可。应注意由于是函数,则可以参数变化而函数名依旧相同,这就成了重载函数。

共3页 首页 上一页 1 2 3 下一页 尾页 跳转到
相关内容
赞助商链接