(1)通过友员来继承,可以把整个派生类或派生类中的某些方法声明为基类的友员,这样就可以访问基类中的私有部分。 (2)利用类定义中的保护部分(protected)。在基类中声明为protected的类成员,可以被派生类继承。 我们先举一个简单的例子来说明继承和派生的关系,然后再详细说明访问属性与控制之间的关系。 //定义一个枚举 enum BugColor{red,green,blue,yelloe,black}; class Bug //定义一个昆虫基类 { protected: int legs; BugColor color; public: //成员函数 Bug(int numlegs,BugColor c); void Draw(); }; //定义一个派生类 class HumBug:public Bug { private: int Frenquency; public: HumBug(int numlegs, BugColor c,int Fren); void Hum(); }; 该例把类HumBug定义为基类Bug的派生类,由于访问属性为public,因此基类Bug中的所有Public成员也是派生类HumBug中的public成员,而基类中的保护成员也是派生类中的保护成员。虽然HumBug定义了自己的方法,但它也继承了基类Bug中定义的方法。因此下面的函数是合法的: void func() { Bug b(8,blue); HumBug h(10,green,1000); b.Draw(); h.Draw(); h.Hum(); } 上例定义了基类Bug的派生类HumBug。实际上,还可以以HumBug为基类,再定义派生类,从而形成类层次结构。当访问属性使用不同的访问说明符时,派生类从基类中继承的访问控制也不一样。上面的例子从基类Bug产生出派生类HumBug,继承了基类中的protected和public成员,形成如图4.1所示的类层次,其中划底线数据和方法是从基类继承来的。

图4-1 访问属性与控制: 综上所述,可以看出,派生类继承了基类中的部分成员。类中的数据成员和成员函数有的可以被类本身存取,有的可以被该类的派生类存取;有的可以被类本身的对象存取,有的则可以被派生类的对象存取。这种访问控制由访问说明符来决定,包括类定义中使用的访问说明符和定义派生类时使用的访问说明符。例如,定义为私有(private)的数据成员或函数只能被类本身的成员存取;而定义为公有(public)的数据成员或函数可以被派生类的成员或派生类的对象存取。见表4-2:
类成员类型 类本身 类对象 派生类 派生类对象 private 允许 不允许 不允许 不允许 public 允许 允许 允许 允许 protected 允许 不允许 允许 不允许
表4-2 基类和派生类的访问控制 从上面的表中可以看出: (1) 类定义本身可以存取声明为protected、public、private类型的数据成员或成员函数。也就是说,在类定义内部,可以存取任何成员。 (2) 一个类的对象只能存取在该类或其基类中声明为public类型的数据成员或函数。 (3) 派生类只能存取在基类中声明为protected、public类型的数据成员或成员函数。 (4) 派生类的对象只能存取在基类中声明为public的数据成员或成员函数。 注意,上面所说的访问控制只是"可以",不是"必定"。因为它还要看在定义派生类时所使用的访问属性。 根据访问属性的不同,从理论上说,派生类和基类的继承关系可以有三种类型,即公有、保护和私有继承类型。但在实际使用时,-般只有两种类型。则公有和私有类型。这是因为保护继承类型所继承的成员为私有或保护成员。不能在类定义之外访问,这样的派生类是没有意义。因此通常只使用以下两种继承类型:公有继承类型和私有继承类型。 7.多态性与虚函数 (1)多态性 面向对象程序设计有三个重要的特色:数据封装、继承和多态性。前两个特性我们已给大家作了详细的讲解,下面主要讲多态性。 用多态性可以实现自上而下的设计方法。这是-种从全局出发,用类的层次结构来模拟客观世界的程序设计力法。通俗地说,多态性是指用一个相同的名字定义不同的函数,这些函数执行不同但又类似的操作,即用同样的接口访问功能不同的函数。前面讲过的函数重载就是一种多态性,这是编译时的多态性,也称静态多态性。多态性还有一种实现方法,就是动态多态性,相对与静态多态性,它的实现代码是在运行是选定的,并且提供了更好的灵活性、问题的抽象性和程序的易维护性,但是它的运行效率相对来说较低。 面向对象的程序设计语言支持多态性。从本质上说,多态性可以引用多个类的实例。利用多态性,程序员可以向一个对象发送消息来完成一系列操作,而不必关心软件是怎样实观这些操作的。在系统设计阶段.当设汁人员决定把某一类型的活动用于一个给定的对象时,并不关心这个对象如何解释这个活动(消息)以及这个方法如何实现,而只关心这个活动对这个对象所产生的作用,C++允许程序员向不同但有关的对象发送同样的消息和完成同样的操作.而让软件系统决定如何为给定的对象完成所需要的操作。 多态性具有两方面的作用。首先.它可以用-种相似的方式来处理相关的概念;其次,多态性也使得程序更易于扩充.当增加一种与已有类相关的类时,利用多态性无需修改程序的其余部分。 (2)虚函数 虚函数是在基类中用关键字virtual来声明需要重载的函数,关键字只能在基类中用,在派生类中就不能在重载的函数前面加上virtual了。 虚函数只需简单的加上一个关键字virtual就可以了,但是为什么要使用虚函数呢?这一点参考上并没有讲清楚,不了解使用虚函数的意义,就不能更深层的了解虚函数。所以在这里有必要跟大家说一说。 先看一个例子: class classbase { public: void afn(void) { cout<<〃here is base class〃<<endl;} }; class classderived:classbase { public: void afn(void) { cout<<〃here is derived class〃<<endl;} }; void fn(classbase * pclassbase) { pclassbase->afn(); } 在执行函数fn()时,两个类的成员函数afn()都有可能被调用。因为C++的继承采用了复制继承的方式。也就是说,派生类的对象要为基类的所有成员分配空间(成员函数则分配函数指针空间),将它们存放在此空间内,然后再存放派生类新增的成员。类classbase和类classderived在内存的存放布局为下图所示:

从图中可以看出,pclassbase->afn()应该调用的是第一个afn(),即classbase::afn(),如果要调用classderived::afn()必须显式指出,这实际上是"一个接口,一种方法",没有多态性。在继承机制下,编译程序有时难以决定调用哪一个重载函数,只有在运行时才能确定.这就是滞后联编。用滞后联编可以实现多态性。 还是看上面的一个例子,我们只在基类classbase的定义中,在成员函数afn()的前面加上一个关键字:virtual。别的声明都不变。下面是主函数main(); int main() { classbase objb; classderived objd; cout<<〃first call:〃afn(&objb); cout<<〃second call:〃<<afn(&objd); return 0; } 在该程序中,函数afn被调用了两次,第一次调用时传递classbase指针,第二次调用时传递classderived指针,程序执行结果如下: first call: here is base; second call: here is derived; 这就是说,通过改变指针参数,可以使同一个函数afn分别调用两个类中的成员函数,从而实现了"一个接口,两种方法",这就是多态性。
|