繼承派生多態
發表時間:2024-01-18 來源:明輝站整理相關軟件相關文章人氣:
[摘要]派生類派生類概述 利用繼承機制,新的類可以從已有的類中派生(有關繼承見下一節“單一繼承”的開始)。那些用于派生的類稱為這些特別派生出的類的“基類”。派生類的說明可以用下面的語法。 語法基類說明::基類表基類表:基類說明符基類表,基類說明符基類說明符:完全類名稱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。為了消除這種冗余,當這些類被繼承時,可以說明為虛擬基類。
有關說明虛擬基類以及帶有虛擬基類的對象是如何組成的,見本章后面的“虛擬基類”。