复习15:类的层次结构

15.1 引言和概述

15.2 多重继承

       使用多重继承可以用多个父类组合出子类。最常见的是继承多个抽象类,即实现多个接口。但使用多重继承也是有风险的,如可能存在歧义性。

15.2.1 歧义性解析

       两个父类中存在相同名字的成员,包括成员变量和成员函数,在子类中使用该名字就需要歧义性解析。有两种方法完成这项工作。

      1. 子类重新定义了这个名字,遮蔽了父类信息,也遮蔽了歧义性。可以通过加上父类限制符的方式访问父类定义的名字。

      2. 子类没有重新定义 这个名字,则需要通过父类限制符指明引用的是哪一个父类成员。class A{ public: int i; virtual void v(){ cout << "v in A" << endl; } }; class B{ public: int i; static int j; virtual void v(){ cout << "v in B" << endl; } }; int B::j = 1; class C : public A,public B{ public: void v(){ cout << "v in C" << endl; } }; void f(){ C *pc = new C(); pc->v(); pc->A::v(); A* pa = pc; pa->v(); pc->A::i = 1; pc->B::i = 2; // cout << pc->i << endl; 产生错误! cout << pc->j << endl; //无歧义!只有一个j. cout << pc->A::i << endl; cout << pc->B::i << endl; }       输出结果如下:复习15:类的层次结构_第1张图片可以看出使用父类限制符访问的永远是父类中的成员,这里的虚函数不会表现出多态(多态只出现在父类指针指向子类对象的时候)。

15.2.2 继承和使用声明

      如果类C的父类A,B都有名为f的函数,但是他们的参数列表不同,想让他们在C中构成一组重载函数该怎么办呢?如果我们不做任何处理的话,出现在C中的不是一组重载函数,而是一组重名函数!这是因为重载函数的解析不会跨越不同类的作用域。更近一步说,来自不同父类的函数之间的歧义性不能依靠参数类型完成解析。为了达到我们的目的,我们在子类C中使用声明将这些函数引进一个公共的作用域中。例如,class A{ public: int f(int); int f(char); }; class B{ public: double f(double) }; class C:public A,public B{ using A::f; using B::f; C f(C); // 这样C中出现了四个重载的f()! };       注,在一个类中使用声明所引用的必须是其父类成员,使用指令是不能出现在类定义里的,也不能对一个类使用。

15.2.3 重复的基类

      如果子类的两个父类有共同的父类,则该子类对象中就会有两个“爷爷" 类的对象。这种情况有时候是有意义的,若想只要”爷爷“类的对象,则使用虚基类(也称虚继承)机制。、

15.2.4 虚基类class A{ }; class B : public virtual A{ }; class C : public virtual A{ }; class D : public B,public C { };   B和C虚继承自A,这样他们的子类D只包含了A的一个对象。我们称A为虚基类。

15.2.4.1 用虚基类的程序设计

      这里有两个要说明的问题。

      1. 虚基类只有一个对象存在于子类中,则意味着虚基类的构造函数只被调用一次,而且是在构造最终子类时显式或隐式调用它。

      2. 如果虚基类中有虚函数,若多次调用该虚函数,则该虚函数被调用多次,这可能与我们的期望不同。如果我们想要虚基类的函数只调用一次,则只能有程序员实现这样的功能。

15.2.5 使用多重继承

      使用多重继承最多的场合是继承抽象类实现接口。似乎应该把抽象类的子类都做成虚继承的以减少抽象类的重复,但实际上并没有这样的要求。除了历史的原因外,使用虚继承会带来时间和空间的额外,因为需要虚函数表的辅助。

15.2.5.1 覆盖虚基类的函数

      虚基类的子类选择覆盖虚基类一部分的函数,如果他们覆盖没有重叠的部分,则他们可以作为一个子类共同的父亲。如果覆盖有重叠,则要求他们的儿子覆盖重叠的部分,以消除歧义。

15.2.6 补充一点

      考虑如下代码的结果class A{ public: int i; }; class B1 : public virtual A{ }; class B2 : public virtual A{ }; class C : public A{ }; class D : public B1, public B2, public C { }; void f(){ D *pd = new D(); pd->B1::i = 1; pd->C::i = 2; cout << pd->A::i << ' '<< pd->B1::i << ' ' << pd->B2::i << ' ' << pd->C::i << endl; pd->A::i = 0; cout << pd->A::i << ' ' << pd->B1::i <<' ' << pd->B2 ::i << ' ' << pd->C::i << endl; }       c++对这种情况未作定义,下面是在VS上测试的结果。这说明了vs采用如下的方式实现:相同的虚基类共享同一个对象,且这个对象为子类的默认成员;非虚基类对象分别有自己的实例,访问他们需要使用父类限制符。

 

15.3 访问控制

15.3.1 保护成员

      这里给出一个建议。应该考虑父类对子类的可见域,给子类太多可见的东西,往往是设计模糊和潜在错误的指示剂。建议尽可能少的使用protected成员变量,使用protected成员函数作为父类给子类的窗口。

15.3.2 对基类的访问

      public派生方式使得子类成为基类的一个子类型(特例),protected和private派生方式用于表示实现细节。protected在子类继续派生子类的情况下非常有用,这使得孙子能够访问到爷爷的东西。private用于将界面进一步具体化。

15.3.2.1 多重继承的访问控制

      在一个派生图中,如果一个名字或者基类可以从多条路径到达,只要有一条路径能够访问它,它就是可以访问的。struct B{ int m; static int sm; }; class D1:public virtual B{}; class D2:public virtual B{}; class D:public D1, private D2{}; void f2(){ D* pd = new D(); B* pb = pd; //可以,通过D1访问 int i = pd->m; //可以,通过D1访问 }  15.3.2.2 使用声明和访问控制

      不能使用声明取得更多信息的访问权。在另一方面,如果某种访问可行,允许将访问权授予其他用户。例如class A{ private: int a; protected: int b; public: int c }; class B:public A{ public: using A::a; //错误,不能获得更多的访问权 using A::b; //可以,将对b的访问权授予了所有用户(因为出现在public范围内) };       使用声明和private或protected派生结合使用,可以描述一种界面,只将某一个类所提供的一部分呈现出来(有点像数据库中的视图),例如。class A{ private: int a; protected: int b; public: int c; }; class C:private A{ public: using B::b; using B::c; };       15.4 运行时类型信息

你可能感兴趣的:(c,工作,数据库,struct,测试,Class)