明辉手游网中心:是一个免费提供流行视频软件教程、在线学习分享的学习平台!

继承派生多态

[摘要]派生类派生类概述 利用继承机制,新的类可以从已有的类中派生(有关继承见下一节“单一继承”的开始)。那些用于派生的类称为这些特别派生出的类的“基类”。派生类的说明可以用下面的语法。 语法基类说明::基类表基类表:基类说明符基类表,基类说明符基类说明符:完全类名称virtual 访问说明符opt 完全...

派生类
派生类概述 
利用继承机制,新的类可以从已有的类中派生(有关继承见下一节“单一继承”的开始)。那些用于派生的类称为这些特别派生出的类的“基类”。派生类的说明可以用下面的语法。 
语法
基类说明::
基类表
基类表:
基类说明符
基类表,基类说明符
基类说明符:
完全类名称
virtual 访问说明符opt 完全类名称 
访问指示符 virtualopt 完全类名称
访问指示符:

private

protected

public
单一继承
在“单一继承”这种最普通的形式中,派生类仅有一个基类,考虑如图9.1所示的关系。


注意图9.1中的从一般到特殊的过程。在类的层次设计中,可以发现一些普遍的特性,即派生类总是同基类有“kind of”关系。在图9.1中书是一种印刷好的文档而一本平装书是一种书。

在图9.1中的另一个值得注意点是Book既是派生类(从PrintedDocument中派生),也是基类(PaperbackBook是从Book派生的)。下面的例子是这种类层次的一个轮廓性的说明。

class PrintedDocument

{

//成员表

};

//Book是从PrintedDocument中派生的

class Book:public PrintedDocument

{

//成员表

};

//PaperbackBook是从Book中派生

class PaperbackBook: public Book

{

//成员表

};

PrintedDocument作为Book的直接基类,它同时也是PaperbackBook的非直接基类。直接基类和非直接基类的区别在于直接基类出现在类说明的基类表中,而非直接基类不出现在基类表中。

每个派生类的说明是在基类的说明之后说明的, 因此对于基类仅只给出一个前向引用的说明是不够的,必须是完全的说明。

在前面的例子中,使用的访问说明符是public。公有继承、私有继承以及保护的继承在第10章“成员访问控制”中讲述。

一个类可以作为很多特别类的基类,如图9.2所示。


在图9.2中的图叫“有向无环图”(DAG)。有一些类是多个派生类的基类。但反过来不是真的:对于任意给的派生类仅有一个直接基类。图9.2描绘了单一继承的结构。

注意:有向无环图并不仅用于单一继承。它们也可以用于多重继承图。这一主题将在下一节中的“多重继承”中论述。

在继承中,派生类含有基类的成员加上任何你新增的成员。结果派生类可以引用基类的成员(除非这些成员在派生类中重定义了)。当在派生类中重定义直接基类或间接基类的成员时,可以使用范围分辨符(::)引用这些成员。考虑下面的代码:

class Document
{
public:
char * Name;//文档名称
void PrintNameOf(); //打印名称
};
//实现类Document的PrintNameOf函数

void Document::PrintNameOf()
{
cout << Name << end ;
}
class Book:public Document
{
public:
Book(char *name, long pagecount);
private:
long PageCount;
};
//class Book 构造函数

Book::Book (char *name, long pagecount)
{
Name=mew char [strlen(name)+1];
strcpy (Name,name);
PageCount=pagecount;
};
注意,Book的构造函数(Book::Book)具有对数据成员Name的访问权。在程序中可以按如下方式创建Book类对象并使用之。

//创建一个Book类的新对象,这将激活构造函数Book:BookBook

LibraryBook ("Programming Windows,2nd Ed",994);

...

//使用从Document中继承的函数PrintNameOf.

LibraryBook.PrintNameOf();如前面例子所示,类成员和继承的数据与函数以一致的方式引用。如果类Book所调用的PrintNameOf是由类Book重新定义实现的,则原来属于类Document的PrintNameOf函数只能用范围分辩符(::)才能使用:

class Book:public Document
{
Book(char *name,long pagecount);
void PrintNameOf();
long PageCount;
};
void Book::PrintNameOf()
{
cout<<"Name of Book:";
Document::PrintNameOf();
}
只要有一个可访问的、无二义性的基类,派生类的指针和引用可以隐含地转换为它们基类的指针和引用。下面的例子证实了这种使用指针的概念(同样也适用于引用):

#include <iostream.h>
void main()
{
Document * DocLib[10]; //10个文档的库
for (int i=0; i<10; ++i)
{
cout<<"Type of document:"
<<"P)aperback,M)agazine,H)elp File,C)BT"
<< endl;
char CDocType;
cin >>CDocType;
switch(tolower(CDocType))
{
case 'p':
DocLib[i]=new PaperbackBook;
break;
case 'm':
DocLib[i]=new Magazine;
break;
case 'h':
DocLib[i]=new HelpFile;
break;
case 'c':
DocLib[i]=new ComputerBasedTraining;
break;
default:
--i;
break;
}
}
for (i=0; i<10; ++i)
DocLib[i]->PrintNameOf();
}
在前面例子的SWITCH语句中,创建了不同类型的对象。这一点依赖于用户对CDocType对象所作出的说明。然而这些类型都是从类Document中派生出来的,故可以隐含地转换为Document*。结果是DocLib成为一个“相似链表”(heterogeneous list)。此链表所包含的是不同种类的对象,其中的所有对象并不是有相同的类型。

因为Document类有一个PrintNameOf函数。因此它能够打印图书馆中每本书的名称,但对于Document类型来说有一些信息会省略掉了(如:Book的总页数,HelpFile的字节数等)。

注意:强制基类去实现一个如PrintNameOf的函数,通常不是一个很好的设计,本章后面的“虚拟函数”中提供了一个可替换的设计方法。

多重继承

C++的后期的一些版本为继承引入了“多重继承”模式。在一个多重继承的图中,派生类可以有多个直接基类。考虑图9.3。


9.3所示的图中,显示了一个CollectibleString类。该类既像Collectible类(一种可包容聚集的类),又像String类。对于派生类需要多个基类的属性的问题,多重继承是一种很好的解决办法。因而也很容易派生出CollectibleCustomer和CollectibleWindow等等。

对于一个特定的程序如果每个类的属性并不是全部要求使用,则每个类可以单独使用或者同别的类联合在一起使用。因此把图9.3所描绘的类层次作为基础,用户很容易组织出不可收集的字符串或可收集的非字符串。对于使用单一继承,则没有这种便利性。

虚基类层次 有一些类层次很庞大,但有很多东西很普遍。这些普遍的代码在基类中实现了,然而在派生类中又实现了特殊的代码。

对于基类来说重要的是建立一种机制,通过这种机制派生类能够完成大量的函数机能。

这种机制通常是用虚函数来实现的。有时,基类为这些函数提供了一个缺省的实现。如在图9.2的Document类层次中,两个重要的函数是Identify和WhereIs。当调用Identify函数时,返回一个正确的标识。对于各种文档来说正确的是:对于Book,调用如doc->Identify()的函数必须返回ISBN编号;而对于一个HelpFile返回产品名和版本号更合理一些。同样,WhereIs函数对于一本书来说应该返回行和书架号,但对于HelpFile就应该返回它的磁盘位置,也许是一个目录和名称。

了解到所有的Identify和WhereIs的函数实现返回的是同种类型的信息,这一点很重要。在这个例子中,恰好是一种描述性字符串。

这些函数可以作为虚拟函数来实现,然后用指向基类的指针来调用,对于实际代码的联结将在运行时决定,以选择正确的Identify和WhereIs函数。

类协议的实现

类可以实现为要强制使用某些协议。这些类称为“抽象类”,因为不能为这种类类型创建对象。它们仅仅是为了派生别的类而存在。

当一个类中含有纯虚拟函数或当他们继承了某些纯虚拟函数却又没有为它们提供一个实现时,该类称为抽象类。纯虚拟函数是用纯说明符定义的虚拟函数。如下:

virtual char *Identify()=0;

基类Document把如下一些协议强加给派生类。

* 为Identify函数提供一个合适的实现

* 为WhereIs函数提供一个合适的实现

在设计Document类时,通过说明这种协议,类设计者可以确保如不提供Identify和WhereIs函数则不能实现非抽象类。因而Document类含有如下说明:

class Document
{
public:
... 
//对派生类的要求,它们必须实现下面这些函数
virtual char *Identify()=0;
virtual char *WhereIs()=0;
...
};
基 类

如前面讨论的,继承过程创建的新的派生类是由基类的成员加上由派生类新加的成员组成。在多重继承中,可以构造层次图,其中同一基类可以是多个派生类的一部分。图9.4显示了这种图。


在图9.4中以图的形象表达了CollectibleString和CollectibleSortable的组成。然而,基类Collectible通过路径CollectibleSortable以及CollectibleString到达类CollectibleSortableString。为了消除这种冗余,当这些类被继承时,可以说明为虚拟基类。

有关说明虚拟基类以及带有虚拟基类的对象是如何组成的,见本章后面的“虚拟基类”。



相关文章