天天看点

类层次结构----读书笔记(上篇)0 前言1 访问控制与继承方式3 多重继承

0 前言

本篇文章将讨论派生类和虚函数如何与其他语言功能相互作用。例如访问控制、名字查找、指针和类型转换等等。 所谓类层次结构也就是类的继承机构,一个有向无环图。这里我们规定基类在上层,子类在下层。例如:

类层次结构----读书笔记(上篇)0 前言1 访问控制与继承方式3 多重继承
类层次结构----读书笔记(上篇)0 前言1 访问控制与继承方式3 多重继承

本文按基础部分和延伸部分进行排版。上篇也就是所谓的基础篇。

1 访问控制与继承方式

类中的成员可以是private、protected或public修饰。 ----如果一个成员是private修饰,它的名字只能由其声明所在类的成员函数与友元使用。 ----如果一个成员是protected修饰,它的名字只能由其声明所在类的成员函数和友元,以及由该类派生类的成员函数和友元使用。 ----如果一个成员是public修饰,它的名字可以由任何函数使用。 下面来说说这些访控制的一贯用法。 class A{ private:        //私用成员 protectted:       //保护成员 public:       //公用成员 }; class中默认为private成员,而struct中默认为public。 一般类中的数据成员或者函数成员,你不希望别人直接访问,就可将其设为私用部分,而在public中留有使用的接口。举个例子就是你口袋的人民币肯定要设为私有成员,因为你肯定不希望别人能够直接访问你的口袋有多少钱。别人要想知道这个信息,肯定只有通过你,你就留下了public访问的接口。数据隐藏的简单公用和私用模型能够很好地满足具体类型设计的需要。 protected控制可以理解为你儿子是你的继承者,如果你口袋的钱这个成员属于protected,那么你儿子是可以直接访问的,这下就会出现一个问题。在你不经意的情况下,你儿子会对你口袋的钱数进行修改。这也就对数据的破坏敞开的大门。不过幸好,class中private修饰为默认,对于大量数据存在的基类,供派生类使用的情况总存在一些替代方案。 同样,继承方式也分为3类:private继承、protectedt继承、public继承。具体操作如下所示: class Derived:继承方式 Base{//..........}; ----private继承,那么Base的public和protected成员只能由Derived的成员函数和友元访问,只有Derived的成员和友元能将Derived* 转换为Base* 。 ----protected继承,Base的public和protected成员能由Derived的成员函数和友元以及由Derived派生出的类的成员函数和友元访问。只有Derived的成员和友元及Derived派生出的类的成员和友元将Derived *转换成Base *。 ----public继承,Base的public成员能由任何函数访问使用,protected成员由Derived的成员和友元及Derived的派生类的成员函数和友元访问。任何函数都可将Derived *转换到Base *。 需要强调的是class的继承方式如果不写则为private继承,struct不写则为public继承。

3 多重继承

这是一个C++有别于Java的地方。Java中只允许单一继承,但可利用实现多个接口,或者利用组合类来达到多重继承的效果。 显然单继承就是只有一个直接基类。多重继承的具体形式: class C:public A,public B{ //...................... };

3.1 歧义性解析

如果两个基类中都包含相同名字的成员函数,例如: class A{      //..............      virtual Info* get_debug_info(); }; class B{     //.............. virtual Info* get_debug_info(); }; class C:public A,public B{//............}; C继承自A,B,则在f()中: void f(C *pf){       Info *tmp = pf->get_debug_info();    //出错,因为歧义       tmp = pf->A::get_debug_info();       //ok       tmp = pf->B::get_debug_info();       //ok } 但是,这样做看起来会很不优美,而且混乱。我们可以用在派生类中定义新函数的方式来消除这类问题。 class C:public A,public B{       //...........       Info *get_debug_info(){             Info * tmp1 = A::get_debug_info();             Info *tmp2 = B::get_debug_info();             return tmp1->merge(tmp2);       } }; 这下可以消除歧义,并且在使用的时候看起来也干净很多。 另外一种情况,若在不同的基类中使用同样的名字是一种精心策划的设计决策,或者用户希望能够基于实际参数类型做出选择,那么我们该怎么做呢?来看下面的例子: class A{ public:      int f(int);     char f(char);     //......... }; calss B{ public:      double f(double);      //......... }; class C:public A,public B{ public:       using A::f;       using B::f;       char f(char);      //此处将隐藏A::f(char);       C char f(C); }; void g(C &tmp){       tmp.f(1);        //A::f(int)       tmp.f('a');      //C::f(char)       tmp.f(2.0);      //B::f(double)       tmp.f(tmp);     //C::f(C) } 使用声明可以组合起一组来自不同基类的和来自派生类的重载函数。

3.2 重复的基类

为了描述多个基类的能力,有时一个类可能多次作为基类。例如: class Base{ public:       int a;       //....... }; class A:public Base{      //....... }; class B:public Base{      //....... }; class C:public A,public B{      //....... };

就会出现如下所示的类层次结构:

类层次结构----读书笔记(上篇)0 前言1 访问控制与继承方式3 多重继承
类层次结构----读书笔记(上篇)0 前言1 访问控制与继承方式3 多重继承

那么C中就会有两个Base类的副本,有出现歧义性的危险: void f(C *p){       p->a = 0;     //错误       p->Base::a = 0;     //错误       p->A::a = 0;        // ok       p->B::a = 0;       //ok } 公共基类不应该表示为两个分离对象,这种情况就该用虚基类来处理。 class A:public virtual Base{          //........ }; class B:public virtual Base{          //........ }; class C:public A,public B{         //........ }; 类层次结构为:

类层次结构----读书笔记(上篇)0 前言1 访问控制与继承方式3 多重继承
类层次结构----读书笔记(上篇)0 前言1 访问控制与继承方式3 多重继承

这时候在C中只有一个Base的副本,减少占用空间的同时还有助于消除歧义。

继续阅读