C++学习从零开始(三) |
同样,这样也将进行隐式类型转换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同名),成员函数就更没有问题 。只用和前面一样,依照上面所说进行类型匹配 测验即可 。应 留神由于是函数,则 可以参数 变迁而函数名依然 雷同,这就成了重载函数 。 虚继承前面已经说了,当生成了AB的实例,它的长度实际应该为A的长度加B的长度再外加AB自己定义的成员所占有的长度 。即AB的实例之所以又是A的实例又是B的实例,是由于一个AB的实例,它既记录了一个A的实例又记录了一个B的实例 。则有这么一种状况--蔬菜和水果都是植物,海洋生物和脯乳动物都是动物 。即继承的两个父类又都从同一个类派生而来 。 假如如下: struct A { long a; }; struct B : public A { long b; }; struct C : public A { long c; }; struct D : public B, public C { long d; }; void main() { D d; d.a = 10; } 上面的B的实例就包括了一个A的实例,而C的实例也包括了一个A的实例 。那么D的实例就包括了一个B的实例和一个C的实例,则D就包括了两个A的实例 。即D定义时,将两个父类的映射元素继承,生成两个映射元素,名字都为D::a,类型都为long A::,映射的偏移值也正好都为0 。 后果main函数中的d.a = 10;将报错, 无奈确认 使用哪个a 。这不是很奇怪吗?两个映射元素的名字、类型和映射的数字都一样!编译器为何就不晓得将它们定成一个,由于它们实际在D的实例中 示意的偏移是不同的,一个是0一个是8 。同样,为了 肃清上面的问题,就书写d.B::a = 1; d.C::a = 2;以 示意不同实例中的成员a 。可是B::a和C::a的类型不都是为long A::吗?但上面说过,成员变量或成员函数它们 本身的名字也会在类型匹配中起作用, 因此关于d.B::a,由于左侧的类型是D,则看右侧,其名字 示意为B,正好是D的父类,先隐式类型转换, 而后再看类型,是A,再次进行隐式类型转换, 而后返回数字 。 假如上面d对应的地址为3000,则d.C::a先将d这个实例转换成C的实例, 因此将3000偏移8个字节而返回long类型的地址类型的数字3008 。 而后再转换成A的实例,偏移0,最终返回3008 。 上面 注明了一个问题,即 盼望从A继承来的成员a惟独一个实例,而不是像上面那样有两个实例 。 假如动物都有个饥饿度的成员变量,很显而易见地鲸鱼应该 惟独填充一个饥饿度就够了, 后果有两个饥饿度就显得很奇怪 。对此,C++提出了虚继承的概念 。其 格局便是在继承父类时在权限语法的前面外加 要害字virtual即可 。
|