第二章抽象數據類型_第1頁
第二章抽象數據類型_第2頁
第二章抽象數據類型_第3頁
第二章抽象數據類型_第4頁
第二章抽象數據類型_第5頁
已閱讀5頁,還剩106頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

數據結構計算機科學系施化吉E-mail:第二章抽象數據類型和C++類抽象數據類型(AbstractDataType,ADT)是用戶在基本數據類型基礎上定義和實現的數據類型。一種抽象數據類型就是定義了一種新的數據元素集合和該數據元素集合上所允許的操作集合。抽象數據類型在更高一級的抽象程度上實現了信息的隱藏和封裝。C++中類的定義體現了抽象數據類型的思想。C++中對于面向對象程序設計的支持,核心部分就是類的定義。2.1抽象數據類型

2.1.1從數據類型到抽象數據類型數據類型

定義:一組性質相同的值的集合,以及定義于這個值的集合上的一組操作的總稱。C語言中的基本數據類型有

charintfloatdoublevoid

字符型整型浮點型雙精度型無值數據類型規定了其值的集合中元素的取值范圍和對這些元素所能采用的操作。

例如:

整型定義了其數據集合中的元素的:

可取的值是機器所能表示的最小負整數和最大正整數之間的任何一個整數。

可用的操作有單目運算符+、-,雙目運算符+、-、*、/、%,關系運算符<、<=、>、>=、==、!=和賦值運算符=等。而抽象數據類型:抽象數據類型定義:是用戶自己定義和實現的數據類型。是指一個數學模型以及定義在該模型上的一組操作。是用以表示應用問題的數據模型,由基本的數據類型組成,并包括一組相關的服務(或稱操作)。

抽象數據類型類似于在計算機機器語言的“位、字節和字”的基礎上引入“字符、整數、浮點數和雙精度數”等數據類型這樣一種方法。這里,“字符、整數、浮點數和雙精度數”等數據類型是對“位、字節和字”的抽象。

因為計算機是使用二進制定點數和浮點數來實現數據的存儲和運算的。從計算機硬件系統的角度看,計算機能處理的數據類型只包括位、字節和字。高級程序設計語言中引入了字符、整數、浮點數、雙精度數后,程序中就可以直接使用“A”,“57”,“1.3E10”等數據表示,而不必使用它們的二進制表示。編譯程序會將這些數據值轉換成二進制碼。

這些數據表示就是二進制數據的抽象。

一個抽象數據類型定義了一種新的數據元素集合和數據元素集合上所允許的操作集合。

在高級程序設計語言中,由基本數據類型可以定義出更高一級的抽象數據類型,如各種表、隊列、圖甚至窗口、管理器等。

下面以“隊列”為例,解釋抽象數據類型。

一個隊列是由若干個元素組成的一個序列以及這個序列上的相關操作所構成。其操作遵循的是“先到先服務”的原則。當新的元素進隊列:加入在隊列的尾部。

當元素出隊列:總是取出隊列頭的元素。

隊列中的元素可以是各種不同類型的對象。它們可以是整數,也可以是字符,也可以是字符串,甚至可以是關于一個學生的記錄。不管是由什么對象組成隊列,都不影響我們對“隊列”本質的理解。“隊列”本質就是“先進先出”。

所以,可把隊列看成是一個抽象數據類型。

注意:對于一個其數據成員完全相同的數據類型,如果對它作不同的“限制”,即定義不同功能的一組操作,就可以形成不同的抽象數據類型。如棧和隊列,可能都是相同的順序表結構,但“限制”不同,則具有不同的服務,因此是不同的抽象數據類型。抽象數據類型與具體應用無關,這樣我們在編程序的時候就可以把注意力集中在數據及其操作的理想模型上。如何描述抽象數據類型?2.1.2抽象數據類型描述

抽象數據類型的特征是把使用和實現分離開來,實行封裝和信息隱藏。每種抽象數據類型的描述應包括:抽象數據類型的名(稱)數據集合的定義每種操作的名稱,該操作的輸入、前置條件、功能、輸出和后置條件等定義。一個抽象數據類型的描述在形式上可繁可簡,描述形式舉例如下:ADT

抽象數據類型名

is

Data

數據元素集合和數據元素間的關系Operation

構造函數數據值的初始化操作1

輸入:用戶輸入的數據值

前置條件:執行此操作前數據所必需的狀態

動作:對數據進行的處理

輸出:操作后的返回值描述

后置條件:系統執行操作后數據的狀態操作2

……操作3……endADT

抽象數據類型名

例2-1

要設計一個擲骰子(zhìtóuzi

)的程序,骰子可設計成一個抽象數據類型Dice.

其數據類型包括:被擲骰子個數N,

擲出骰子的總點數,

每個骰子的點數;其操作包括:擲骰子,

返回該次投擲的骰子總點數,

打印所擲每個骰子的點數。ADTDiceisData//數據集合該次投擲骰子的個數。這是一個大于或等于1的整數。

該次擲出的總點數。這是一個大于等于N且小于等于6N的整數。該次擲出的每個骰子的點數。這是一個表,表中的數值均為1到6的整數。

Operation//操作

Constructor

構造函數確定被投擲骰子的個數

Toss//擲骰子

輸入:無前置條件:無動作:擲骰子并計算總點數輸出:無后置條件:投擲的總點數及每個骰子的點數

Total//總點數輸入:無前置條件:無動作:檢索該次投擲的總點數數據項輸出:返回該次投擲的總點數后置條件:無

DisplayToss//各骰子的點數輸入:無前置條件:無動作:打印該次投擲的各骰子的點數輸出:無后置條件:無endADTDice本章的其他內容,同學們在上學期已經學過,這里就不再重復。但請同學們注意復習,特別是有關模板的內容。結束謝謝!2.2類與對象的基本概念2.2.1類與對象人類是一個類(class)。張三是人,李四是人,都是人類的實例(instance),或稱對象(object)。一個類描述一類事物以及該類事物所應具有的屬性,如人有身長、體重、文化程度、性別、年齡等。一個對象是類的一個實例,它具有確定的屬性值,如張三身高1.80米,體重70公斤,大學本科,男,21歲。人類只有一個,人類的實例可以有無數個。對象是具體的,對象可以被創建和銷毀。類是抽象的,類是無所不在的。

例如桌子是一個類,人們不斷打造各種尺寸和各種風格(屬性)的桌子(桌子的實例),打造桌子,又不斷毀壞桌子。年復一年,舊的去了,新的又來,但桌子的概念沒變。它是一個抽象的概念。應該稱它為桌子類,以區別于打造的具體桌子。在計算機領域中,對象是指在應用問題中出現的各種實體、事件、規格說明等。它是由一組屬性值和在這組值上的一組服務(或稱操作)構成的。其中,屬性值確定了對象的狀態。例如,一個顯示在計算機屏幕上的圓,作為一個幾何對象,它是由圓心坐標、半徑長度、邊線顏色和內部顏色等屬性值來確定位置、大小、顏色等狀態。可以通過對象的服務來改變該對象的屬性值。例如,服務move(x,y),將一個圓移動到由實數x,y所指定的圓心的新位置上。服務setRadius(r)、改變圓形的半徑。服務setEdgeColor(c),改變圓形的邊的顏色和其內部顏色。

在面向對象的方法中,類是具有相同操作(服務)與相同數據格式(屬性)的對象的集合。類中的每一個對象稱為該類的一個實例(instance)。有一個計算機電子郵件系統,其主要工作涉及到發信人寫信、收信人看信,還有電子郵件系統中信的收(receive)、發(send)及存儲(store)等方面。

例如,你在計算機上寫一封信給你的同事John,那么這封信本身是一個對象。它具有一般信所有的共性。例如,有信的內容和允許你處理的一些方法或動作(讀信、寫信、發信等)。信的內容在計算機中稱為數據,而需要處理的一些方法和動作,在計算機里則統稱為“操作”,將這些信的共性匯集起來就有了對象這一概念。用計算機軟件的術語可描述為公式對象=數據+動作所有的信的集合就構成了類,信中的內容不同(即對象的屬性值不同)。類中的“信”都具有相同的服務:發信。發送英文信和發送日文信的方式是一樣的。處理英文信和處理日文信有相同的方式,還有一些各自獨有的方式。那么,如果建立兩套平行的信件處理機制,顯然是不經濟的。可以由“信”這個類來定義兩個類:“英文信”和“日文信”,它們保留了“信”類的服務,并添加上各自獨立的服務。這種“保留”稱為“繼承”。

“信”類稱為基類,基類又可稱為父類、超類或泛化類。它具有一般信件的公共操作,讀、寫、發送信。“英文信”、“日文信”稱為派生類,又可稱為子類或特化類。它們繼承了其超類“信”和讀、寫、發送等操作,但同時又加上它們自己的“英文”和“日文”特定操作。類、派生類和實例的關系如圖2-1所示。

繼承和重用是相輔相成的兩個概念。要制造新的電冰箱,可以有兩種選擇:

一種是從草圖開始;

另一種是對現有的電冰箱的型號加以改進。也許現有的型號已經令人比較滿意,但如果再增加兩個功能,會更加完美。

在原有的型號基礎上增加兩組電路,很快就制造出來新的電冰箱了,被賦予一種新的型號。這就是繼承和重用。

繼承是一種聯結類的層次模型,并且允許和鼓勵類的重用。

層次結構的上層(或祖先類)最具有通用性。下層部分,即后代具有特殊性。

類可以從它的祖先那里繼承方法和實例變量(即對象中可訪問的數據),也可以修改或增加新的方法使之更符合特殊的需要。

2.2.2消息與合作一個對象內具有過程和數據。外部的用戶或對象對對象提出的請求,可以稱為對該對象發送消息。在電子郵件系統的例中,對象“信”具有讀信、寫信或發信等操作,是由系統設計者開發并由對象“信”提供給用戶的,用戶可以發送一個消息(或稱為請求)來請求所需的服務。在面向對象的方法中,另一個經常用的概念就是“合作”。

合作是指兩個對象之間共同承擔職責來達到某一目標。當你告訴對象“信”,你想把它發送給John時,你此時的作用就是一個客戶。對象“信”在這里相當于一個服務器。它提供給你一個服務。

你還可以要求別的服務,如讀、寫信等。很多人用客戶/服務器模型來描述面向對象模型。在這個模型中,各個對象具有完成不同任務的職責,客戶對象可以通過服務器對象來完成一些任務。這里把兩個對象共同承擔職責稱為“合作”。2.2.3多態性多態性機制是指允許不同類的對象對同一消息作出響應。以前面提到的電子郵件系統為例,現有局域網類對象LAN和寬域網類對象WAN,這兩類對象均可對來自對象“信”的同一消息“發送給John”作出響應。即對同一個“發送給John”操作,可以有不同的實現方法。可以通過TCP-IP協議來找到John,也可通過X.25協議來找到John,這是在系統運行時由系統決定的。這種動態決定實現方法的機制稱為動態聯編(dynamicbinding)。

一個多態性的例子:

學生類應該有一個計算成績的操作。大學生繼承了中學生。對于中學生,計算成績的操作是對語文、數學、英語等課程的計算,而對于后繼的大學生,計算成績的操作是對高等數學、計算機、普通物理等課程的計算。

多態性引用靜態類型和動態類型。多態引用的動態類型可以在程序執行期間在實例之間進行變化。

在強類型面向對象環境中,運行時系統保持所有多態引用自動地和它們的動態類型相聯結。

靜態類型是在程序上下文中由實體說明決定的。在編譯時,系統知道并且決定對象在運行時可接受的有效類型。這一決定是通過分析繼承圖實現的。

2.3面向對象的程序設計方法

傳統的程序設計技術采用的是算法分解。算法分解就是功能分解,該方法將軟件看成一個處理過程,將該過程分解成若干個步驟的模塊。一般都是在項目被分解成了功能模塊后,再來考慮數據結構。

面向對象的程序設計技術建立在對象分解的基礎上,以類設計為核心。面向對象分解將軟件看成有良好定義的對象組成的集合。這些對象是應用領域中實體的模型化體現。這些對象以及對象之間的相互作用構成了一個軟件系統。當系統已被分解成為若干個對象之后,再去考慮功能的分解。

面向對象分解最主要的優點在于它增進了軟件的可重用性。面向對象分解同面向算法分解相比較更直觀。

程序設計階段應給出所有對象的描述,這是構成程序的主要部分。同時,設計階段也描述對象間相互作用的控制。對象設計階段明確所有在程序中將要用到的對象,并給出每個類的定義,再設計出一些程序模塊對類進行測試。類可以獨立于系統之外測試,這是面向對象程序設計的一大特色。

處理控制設計階段完成程序的框架設計,它使用自頂向下方法創建主程序和子程序來控制對象間的相互作用。

2.4

C++類與對象C++對于面向對象程序設計的支持,核心就是類的定義。類由多個存放數據值的成員和加工數據的運算組成。這些運算稱為成員函數,又稱為方法。成員函數用來響應發送給對象的消息。消息就是對成員函數的調用。定義了一個類以后,就可以用該類的名字來聲明該類的對象。

類可以分為兩個部分:

公共(public)部分描述用戶使用類的界面。對于在public域中聲明的數據成員和成員函數,程序中其它類的對象或操作都能請求該類的對象調用它們,該界面使用戶不必了解對象的內部細節而使用對象。

私有(private)部分由幫助實現數據抽象的數據和內部操作組成。私有部分包括private域和protected域,這兩個域中聲明的數據成員和成員函數只能由該類的對象和成員函數以及聲明為友元(friend)的函數或類的對象訪問。

例2-2

一個定義類、使用類的例子//TIME1.H//類Time的聲明//成員函數在TIME1.cpp中定義#ifndefTIME1_H#defineTIME1_H//抽象數據類型Time的定義classTime{public:Time();//默認構造函數voidsetTime(int,int,int);//設置時、分、秒voidprintStandardTime();//打印標準格式的時間private:inthour;//0-23intminute;//0-59intsecond;//0-59};#endif//TIME1.cpp//類Time的成員函數的定義#include<iostream.h>#include“TIME1.H”/*類Time的構造函數把每一個數據成員初始化為零,從而保證所有的類Time的對象具有穩定的初始狀態*/Time::Time(){hour=minute=second=0;}/*設定一個新的時間值,檢查數據值的合法性,將非法值設置為零*/voidTime::setTime(inth,intm,ints){Hour=(h>=0&&h<24)?h:0;Minute=(m>=0&&m<60)?m:0;Second=(s>=0&&s<60)?s:0;}//按標準格式輸出時間voidtime::printStandardTime(){cout<<((hour==0||hour==12)?12:hour%12)<<“:“<<(minute<10?“0“:““)<<minute<<“:“<<(second<10?“0“:““)<<second<<(hour<12?“AM“:“PM“);}//Example2-2.cpp//使用類Time的程序//注:與文件TIME.cpp一起編譯#include<iostream.h>#include“TIME1.h//測試類Time的程序main(){Timet;//類Time的實例對象tcout<<“Theinitialstandardtimeis“;t.printStandardTime();t.setTime(14,25,9);cout<<“\nstandardtimeaftersetTimeis“;t.printStandardTime();t.setTime(70,70,70);//試圖設定非法值cout<<”\nAfterattemptinginvalidsettingsstandardtimeis”;t.printStandardTime();cout<<endl;return0;}2.5構造函數和析構函數

構造函數是在創建類的對象時自動調用的。類的數據成員不能在定義類的時候初始化,而必須在類的構造函數中初始化,或者在建立類的對象后再設置它們的值。

構造函數不能自動返回值類型和返回值。可以重載構造函數以便用各種方法初始化類的對象。構造函數可以使用默認參數。

給構造函數提供默認參數的好處是:即使在調用構造函數的時候沒有提供參數值,也會確保按照默認的參數值對對象進行初始化。

當一個構造函數所有參數都是默認參數時,該構造函數是默認的構造函數,也就是說,在調用這樣的構造函數時可以不提供任何參數。例2-3初始化類Time的對象

//TIME2.H//類Time的聲明//成員函數在TIME2.cpp中定義#ifndefTIME2_H#defineTIME2_HclassTime{public:Time(int=0,int=0,int=0);//默認構造函數

voidSetTime(int,int,int);voidprintStandardTime();private:inthour;intminute;intsecond;};#endif

//TIME2.cpp//類Time的成員函數的定義#include<iostream.h>//初始化私有數據成員的構造函數//默認值是0Time::Time(inthr,intmin,intsec){hour=(hr>=0&&hr<24)?hr:0;minute=(min>=0&&min<60)?min:0;second=(sec>=0&&sec<60)?sec:0;}voidTime::SetTime(inth,intm,ints){hour=(h>=0&&h<24)?h:0;minute=(m>=0&&m<60)?m:0;second=(s>=0&&s<60)?s:0;}voidTime::printStandardTime(){cout<<((hour==0||hour==12)?12:hour%12)<<“:“<<(minute<10?“0“:““)<<minute<<“:“<<(second<10?“0“:““)<<second<<(hour<12?“AM“:“PM“);}#include<iostream.h>#include“TIME2.H”main(){Timet1,t2(2),t3(15,30),t4(12,20,40),t5(28,70,99);cout<<“Constructedwithallargumentsdefaulted:\n“;t1.printStandardTime();cout<<“\nhourspecified;minuteandseconddefaulted:\n“;t2.printStandardTime();cout<<“\nhourandminutespecified;minutedefaulted:\n“;t3.printStandardTime();cout<<“\nhour,minuteandsecondspecified:\n“;t4.printStandardTime();cout<<“\nallinvalidvaluesspecified:\n“;t5.printStandardTime();cout<<endl;return0;}輸出結果:Constructedwithallargumentsdefaulted12:00:00AMhourspecified;minuteandseconddefaulted:2:00:00AMhourandminutespecified;minutedefaulted:3:30:00PMhour,minuteandsecondspecified:12:20:40PMallinvalidvaluesspecified:12:00:00AM

如果沒有為類定義構造函數,那么編譯器就會為該類添加一個默認構造函數。默認構造函數并不作任何初始化工作,所以在建立該類的對象時不能保證有一個確定的初始狀態。

析構函數是類的一個特殊的成員函數,析構函數的標志是在類的名字的前面加上字符“”。

析構函數是構造函數的反向操作。當撤銷一個類的對象時,該類的析構函數就自動被調用,在系統回收對象所占用的內存空間之前先做的一些清理工作。析構函數沒有參數和返回值。

一個類只能有一個析構函數,重載析構函數是不允許的。當類的對象包含動態分配的內存時,為類提供析構函數是合適的。

析構函數和構造函數通常是被自動調用的,其調用順序取決于建立和撤銷對象的順序。一般來說,調用析構函數的順序與調用構造函數的順序相反。但是,對象的作用域會改變析構函數的順序。對聲明為全局的對象來說,其構造函數在聲明該對象的時候調用,

相應的析構函數在程序退出該對象的作用域的時候被調用。靜態局部對象的構造函數只在聲明該對象的時候調用一次,相應的析構函數在程序終止的時候被調用。

例2-4

類ConstructAndDestruct

//CONSTRUCT.H//類ConstructAndDestruct的定義//成員函數在CONSTRUCT.CPP中定義#ifndefCONSTRUCT_H#defineCONSTRUCT_HclassConstructAndDestruct{public:ConstructAndDestruct(int);//構造函數~ConstructAndDestruct();//析構函數private:intdata;};#endif(接下頁)//CONSTRUCT.CPP//類ConstructAndDestruct的成員函數的定義#include<iostream.h>#include“construct.h“ConstructAndDestruct::ConstructAndDestruct(intvalue){data=value;cout<<“Object“<<data<<“constructor“;}ConstructAndDestruct::~ConstructAndDestruct(){cout<<“Object“<<data<<“destructor“<<endl;}//Example2-4.CPP//演示構造函數和析構函數的調用順序#include<iostream.h>#include“construct.h“voidconstruct(void);//函數原型ConstructAndDestructfirst(1);//全局對象(接下頁)main(){

cout<<“(globalconstructbeforemain)\n“;ConstructAndDestructsecond(2);//局部對象cout<<“(localautomaticinmain)\n“;

staticConstructAndDestructthird(3);//局部對象

cout<<“(localstaticinmain)\n“;

construct();//調用建立對象的函數

ConstructAndDestructfourth(4);//局部對象

cout<<“(localautomaticinmain)\n“;

return0;}(接下頁)//建立函數的對象voidconstruct(void){ConstructAndDestructfifth(5);cout<<“(localautomaticinconstruct)\n“;ConstructAndDestructsixth(6);cout<<“(localstaticinconstruct)\n“;ConstructAndDestructseventh(7);cout<<“(localautomaticinconstruct)\n“;}輸出結果:Object1constructor(globalconstructbeforemain)Object2constructor(localautomaticinmain)Object3constructor(localstaticinmain)Object5constructor(localautomaticinconstruct)Object6constructor(localstaticinconstruct)Object7constructor(localautomaticinconstruct)

(接下頁)Object7destructorObject6destructorObject5destructorObject4constructor(localautomaticinmain)Object4destructorObject2destructorObject3destructorObject1destructor2.6工具函數

并不是所有的成員函數都要設定成公有的。一些成員函數可以是私有的,它們用作其它成員函數的工具函數。成員函數可以分為許多種:

讀取和返回私有數據成員值的函數;設定私有數據成員值的函數;實現類的特點的函數;完成類的各種基本操作的函數(如初始化類的對象、給類的對象賦值、實現類與內部類型或與其它類之間的轉換,處理類的對象所占用的內存等等)。訪問函數可以讀取和顯示數據。訪問函數還經常用來測試條件的真假,這類函數通常叫做判斷函數。函數IsEmpty是一個判斷函數,它用于諸如鏈表、堆棧或隊列等等的包容器類。IsFull判斷函數用于測試包容器對象中是否還有剩余的空間。例2-5的程序演示了工具函數的使用。工具函數不是類的接口,而是類的私有成員,用于支持類的公有成員函數的操作。類的客戶不能直接使用工具函數。

例2-5

演示工具函數的使用

//SALESP.H//類SalesPerson的定義//成員函數在SALESP.CPP中定義

#ifndefSALESP_H#defineSALESP_HclassSalesPerson{public:SalesPerson();//構造函數

voidsetSales();//由用戶提供售貨額

voidprintAnnualSales();private:

doublesales[13];//12個月的售貨額

doubletotalAnnualSales();//工具函數};#endif//SALESP.CPP//類SalesPerson的成員函數#include<iostream.h>#include<iomanip.h>#include“salesp.h“//構造函數對數組的初始化SalesPerson::SalesPerson(){for(inti=0;i<=12;i++)sales[i]=0.0;}//設置十二個月銷售額的函數voidSalesPerson::setSales(){inti; for(inti=1;I<=12;I++){

cout<<“Entersalesamountformonth“<<i<<“:“;

cin>>sales[i];

}}//Example2-5.CPP//演示工具函數的使用//與SALESP.CPP一起編譯#include“salesp.h“main(){SalesPersons;//建立類SalesPerson的對象ss.setSales();s.printAnnualSales();

return0;}輸出結果:Entersalesamountformonth1:5314.76Entersalesamountformonth2:4292.38Entersalesamountformonth3:4589.83Entersalesamountformonth4:5534.03

Entersalesamountformonth5:4376.34Entersalesamountformonth6:5698.45Entersalesamountformonth7:4439.22Entersalesamountformonth8:5893.57Entersalesamountformonth9:4909.67Entersalesamountformonth10:5123.45Entersalesamountformonth11:4024.97Entersalesamountformonth12:5923.92Totalannualsalesare:$60120.592.7繼承

繼承是軟件重用的一種形式。實現的方法就是從現有的類(基類)建立新的類(派生類)。新類繼承了現有類的屬性和行為,并且為了使新類具有自己所需的功能,新類還可以對這些屬性和行為進行修飾。軟件重用縮短了程序的開發時間,使開發人員可以重用已經測試和調試好了的高質量的軟件。在建立一個派生類時,程序員可以讓其繼承基類的數據成員和成員函數,而不必重新編寫新的數據成員和成員函數,派生類本身也可能成為未來派生類的基類。

用類似的方法處理基類對象和派生類對象是可能的。基類的屬性和行為表述了基類對象和派生類對象的共性。從基類公有派生出來的所有對象都可以作為基類對象處理。

例2–6

類point的定義和派生類circle//POINT.H//類point的定義#ifndefPOINT_H#definePOINT_H#include<iostream.h>classPoint{friendostream&operator<<(ostream&,constpoint&);public:Point(float=0,float=0);//默認的構造函數voidsetPoint(float,float);//設置坐標值floatgetX()const{returnx;}floatgetY()const{returny;}protected://允許派生類訪問

floatx,y;//點(類Point的對象)的x和y坐標};#endif

//POINT.CPP//定義類Point的成員函數#include<iostream.h>#include"point.h"http://類Point的構造函數Point::Point(floata,floatb){x=a;y=b;}//設置點的x和y坐標voidPoint::setPoint(floata,floatb){x=a;y=b;}//用重載的流插入運算符輸出點的坐標ostream&operator<<(ostream&output,constPoint&p){ output<<'['<<p.x<<","<<p.y<<']';

returnoutput;//使得能夠連續調用}//CIRCLE.H//類Circle的定義#ifndefCIRCLE_H#defineCIRCLE_H#include<iostream.h>#include<iomanimp.h>#include"point.h"classCircle:publicPoint{friendostream&operator<<(ostream&,constCircle&);public://默認的構造函數Circle(floatr=0.0,floatx=0,floaty=0);voidsetRadius(float);//設置半徑值floatgetRadius()const;//返回半徑值floatarea()const;//計算圓的面積protected:

floatradius;};#endif

//CIRCLE.CPP//定義類Circle的成員函數#include"circle.h“//先用成員初始化值調用類Point的構造函數,//然后再調用類Circle的構造函數初始化半徑值Circle::Circle(floatr,floata,floatb):Point(a,b)//調用基類的構造函數{radius=r;}//設置圓的半徑值floatCircle::getRadius()const{returnradius;}

//計算圓的面積floatCircle::area()const{return3.14159*radius*radius;}

//以Center=[x,y];Radius=#.##形式輸出一個圓ostream&operator<<(ostream&output,constCircle&c){ output<<"Center=["<<c.x<<","<<c.y <<"];Radius="<<setiosflags(ios::showpoint) <<setprecision(2)<<c.radius;

returnoutput;//使得能夠連續調用}CIRCLE-1.CPP//把基類指針強制轉換為派生類指針#include<iostream.h>#include<iomanip.h>#include"point.h"#include"circle.h“intmain(){Point*pointPtr,p(3.5,5.3);Circle*circlePtr,c(2.7,1.2,8.9);

cout<<“Pointp:”<<p<<“\nCirclec:”<<c<<endl;/*Circle的對象還是作為Circle對象處理,但是用了一些類型轉換*/pointPtr=&c;//把Circle對象的地址賦給pointPtrcirclePtr=(Circle*)pointPtr;//把基類指針轉換為派生類指針

cout<<"\nAreaofc(viacirclePtr):" <<circlePtr->area()<<endl;//危險:把Point的對象作為Circle對象處理

pointPtr=&p;//把Point對象的地址賦給pointPtrcirclePtr=(Circle*)pointPtr;//把基類指針轉換為派生類指針

cout<<"/nRadiusofobjectcirclePtrpointsto:"<<circlePtr->getRadius()<<endl;

return0;}

輸出結果:Pointp:[3.5,5.3]Circlec:Center=[1.2,8.9];Radius=2.70Areaofc(viacirclePtr):23RadiusofobjectcirclePtrpointsto:9.5e-039

從一個基類派生一個類時,繼承基類的方式除了公有繼承以外,還有受保護(protected)繼承和私有繼承(private),后兩種繼承不常用。

派生類不繼承基類的構造函數和賦值運算符,但是派生類的構造函數和賦值運算符能調用基類的構造函數和賦值運算符。派生類的構造函數總是先調用其基類構造函數來初始化類中的基類成員。

如果省略了派生類的構造函數,那么就由默認構造函數調用基類的構造函數。析構函數的調用順序和調用構造函數的順序相反,因此派生類的析構函數在基類析構函數之前調用。

2.8this指針的使用

在C++中,每個對象都有一個指向自身的指針。該指針稱為this指針。在引用對象內部的成員時,this指針是一個隱含的參數,即不需顯式寫出它,就可直接訪問對象內部的成員。當然,也可以明確地使用this指針。

每一個對象都可以通過使用this指針來確定其自身的地址。這就是為什么當成員函數引用該類的一個特定對象的另一個成員時,C++能保證該函數能正確地引用了該對象。例2-7

用this指針引用對象的成員

//Example2-7.cpp#include<iostream.h>classUsingThis{public:UsingThis(int=99);

voidprint()const;private:inty;};UsingThis::UsingThis(inta){y=a;}//構造函數voidUsingThis::print()const{

cout<<”y=”<<y<<”\nthis->y=”<<this->y<<”\n(*this).y=”<<(*this).y<<’\n’;}main(){UsingThisu(88);u.print();return0;}輸出結果:y=88this->y=88(*this).y=88

2.9虛函數、多態性以及動態聯編2.9.1虛函數和多態性

虛函數(virtualfunction)和多態性(polymorphism)使得設計和實現易于擴展的系統成為可能。

在程序開發中,不論類是否已經建立,程序員都可以利用虛函數和多態性先編寫處理這些類的對象的程序。如果有些類要從基類派生,那么程序可以提供操作基類對象的通用框架,然后由派生類對象對該框架作很好的加工。例如,一個多邊形可以是矩形(Rectangle)、正方形(Square)、三角形(Triangle)等,它們都可成為多邊形類(Polygon)的派生類。為了高效的繪制各自的形狀,每一個類都需要有一個自己的draw函數,這些函數的程序代碼是不相同的。當需要繪制一個形狀時,不管它是什么形狀,把它作為基類polygon的對象來處理是再好不過了。然后,只需要簡單的調用基類polygon的函數draw,并讓程序動態的確定使用哪一個派生類的draw函數。

為了使這種方法可行,我們把基類中的函數聲明為虛函數draw,然后在每個派生類中重新定義函數draw,使之能夠繪制合適的形狀。虛函數的聲明方法是在基類的函數原型前加上關鍵字virtual。

在類的繼承層次中,如果某一個類的定義里聲明了一個虛函數,那么從該類之后的繼承層次結構中都是虛函數。

沒有定義虛函數的派生類就簡單地繼承其直接基類的虛函數;如果在派生類中重新定義虛函數,那么重新定義的虛函數應當與其基類的虛函數有相同的返回類型,參數個數和參數類型。如果在基類的虛函數原型中加上記號“=0”,該虛函數就成為純虛函數。

純虛函數的實現代碼應當由派生類提供,記號“=0”使得基類成為一個抽象類,抽象類不能用來建立實例化的對象。其唯一用途是為其他類提供合適的基類,其他類可從它這里繼承接口。例如,基類polygon有一個純虛函數virtualvoiddraw()=0,因此polygon就成為抽象類,它沒有實例化對象,也不用來建立實例化對象,所有操作都沒有實現代碼。基類polygon作為抽象類,將使得draw成為所有的派生類的接口。

C++支持多態性,所謂多態性是指“一個接口,多個算法”,即允許把一個接口用于一類行為。不同的通過繼承而相關的類,它們的對象能夠對同一個函數調用作出不同的響應。

例如,有一個程序定義了三種不同數據類型的順序表:一個用于整數型的值,一個用于浮點型的值,一個用于長整數型的值。根據多態性,我們可以為Insert()和Remove()各創建三個函數,編譯程序根據調用Insert()和Remove()時所帶參數的數據類型,選擇適當的實現函數。

多態性是通過虛函數實現的,當通過基類指針引用一個虛函數時,C++會在與對象相關聯的派生類中正確的選擇和使用重定義的函數。

使用虛函數和多態性能夠使成員函數的調用根據接收到該調用的對象的類型產生不同的動作。

多態性給了程序員極大的靈活性。多態性提高了軟件系統的可擴展性。

盡管不能實例化抽象類的對象,但可以聲明抽象(基)類的指針。當實例化了具體類的對象后,可以用這種指針使派生類對象具有多態操作的能力。

例2-8

用虛函數和多態性計算工資//EMPLOY2.H//抽象基類Employee#ifndefEMPLOY2_H#defineEMPLOY2_HclassEmployee{public: Employee(const

char*,const

char*);~Employee();

constchar*getFirstName()const;

constchar*getLastName()const;//純虛函數使類Employee成為抽象基類virtualfloatearnings()const=0;//純虛函數virtualvoidprint()const=0;//純虛函數

private:char*firstName;

char*lastName;};#endif

//EMPLOY2.CPP//定義抽象基類Employee成員函數//注:虛函數沒有定義#include<iostream.h>#include<string.h>#include<assert.h>#include"employ2.h"/*構造函數動態地為名和姓分配內存,并用函數strcpy把名和姓拷貝到對象中*/Employee::Employee(constchar*first,constchar*last){firstName=new

char[strlen(first)+1];

assert(firstName!=0);//測試內存分配是否成功strcpy(firstName,first);lastName=newchar[strlen(last)+1]; assert(lastName!=0);//測試內存分配是否成功strcpy(lastName,last);}

//析構函數釋放動態分配的內存Employee::~Employee(){delete[]firstName;delete[]lastName;}//返回指向雇員名的指針constchar*Employee::getFirstName()const{ /*const限定符防止調用者修改私有數據。為防止引用沒有定義的指針,

調用者應該在析構釋放內存之前拷貝返回的字符串*/

returnfirstName;//調用者必須釋放內存}

//返回指向雇員姓的指針constchar*Employee::getLastName()const{/*const限定符防止調用者修改私有數據。為防止引用沒有定義的指針,調用者應該在析構釋放內存之前拷貝返回的字符串*/

returnlastName;//調用者必須釋放內存}//BOSS1.H//從類Employee派生出來的類Boss#ifndefBOSS1_H#defineBOSS1_H#include"employ2.h"

classBoss:publicEmployee{public:Boss(constchar*,constchar*,float=0.0);voidsetWeeklySalary(float);virtualfloatearnings()const;virtualvoidprint()const;private: floatweeklySalary;};#endif//BOSS1.CPP//定義類Boss的成員函數#include<iostream.h>#include"boss1.h“

//類Boss的構造函數Boss::Boss(constchar*first,constchar*last,floats) :Employee(first,last)//調用基類的構造函數{weeklySalary=s>0?s:0;}//設置老板的工資voidBoss::setWeeklySalary(floats){weeklySalary=s>0?s:0;}//返回老板工資floatBoss::earnings()const{returnweeklySalary;}//打印老板的姓名voidBoss::print()const{cout<<"\nBoss:"<<getFirstName()<<''<<getLastName();}MIS1.H//從類Employee派生出來的類CommissionWorker#ifndefCOMMIS1_H#defineCOMMIS1_H#include"employ2.h“classCommissionWorker:publicEmployee{public:CommissionWorker(constchar*,constchar*,float=0.0,float=0.0,int=0);voidsetSalary(float);voidmission(float);voidSetQuantity(int);virtualfloatearnings()const;virtualvoidprint()const;private:floatsalary;//每周的基本工資 floatcommission;//每件產品的回扣量 intquantity;//一周的銷售量};#endifMIS1.CPP//定義類CommissionWorker的成員函數#include<iostream.h>#include"commis1.h"http://類CommissionWorker的構造函數missionWorker(constchar*first, constchar*last,floats,floatc,intq):Employee(first,last)//調用基類的構造函數{salary=s>0?s:0;commission=c>0?c:0;quantity=q>0?q:0;}//設置銷售員每周的基本工資voidCommissionWorker::setSalary(floats){salary=s>0?s:0;}//設置銷售員的回扣額voidmission(floatc){ commission=c>0?c:0; }//設置銷售員的銷售量voidCommissionWorker::SetQuantity(intq){quantity=q>0?q:0;}//計算銷售員的收入floatCommissionWorker::earnings()const{returnmission*quantity;}//打印銷售員的姓名voidCommissionWorker::print()const{ cout<<"\nCommissionWorker:"<<getFirstName()<<''<<getLastName();}//PIECE1.H//從類Employee派生出來的類PieceWorker

#ifndefPIECE1_H#definePIECE1_H#include"employ2.h“classPieceWorker:publicEmployee{public: PieceWorker(constchar*,constchar*,float=0.0,int=0);voidsetWage(float);

voidSetQuantity(int);

virtualfloatearnings()const; virtualvoidprint()const;private:

floatwagePetPiece;//每件產品的報酬

intquantity;//一周生產的產品數量};#endif

//PIECE1.CPP//定義類PieceWorker的成員函數#include<iostream.h>#include"piece1.h“}//類PieceWorker的構造函數PieceWorker::PieceWorker(constchar*first,constchar*last,floatw,intq) :Employee(first,last)//調用基類的構造函數{wagePetPiece=w>0?w:0; quantity=q>0?q:0;//設置每件產品的報酬voidPieceWorker::setWage(floatw){wagePerPiece=w>0?w:0;}//設置生產的產品數量voidPieceWorker::SetQuantity(intq){quantity=q>0?q:0; }//計算計件工的收入floatPieceWorker::earnings()const

{returnquantity*wagePetPiece;}

//打印計件工的姓名voidPieceWorker::print()const{

cout<<"\nPieceWorker:"<<getFirstName()<<''<<getLastName();}//HOURLY1.H//從類Employee派生出來的類HourlyWorker

#ifndefHOURLY1_H#defineHOURLY1_H#include"employ2.h"

classHourlyWorker:publicEmployee{public:HourlyWorker(constchar*,constchar*,float=0.0,float=0.0);

voidsetWage(float); voidsetHours(float);

virtualfloatearnings()const;virtualvoidprint()const;private:floatwage;//每小時報酬

floathours;//一周工作時數};#endif

//HOURLY1.CPP//定義類HourlyWorker的成員函數#include<iostream.h>#include"hourly1.h“//類HourlyWorker的構造函數HourlyWorker::HourlyWorker(constchar*first,constchar*last,floatw,floath) :Employee(first,last)//調用基類的構造函數{wage=w>0?w:0; hours=h>=0&&h<168?h:0;}//設置每小時報酬voidHourlyWorker::setWage(floatw){ wage=w>0?w:0; }//設置工作時數voidHourlyWorker::setHours(floath){ hours=h>=0&&h<168?h:0;}//返回小時工的收入floatHourlyWorker::earnings()const

{returnwage*hours;}//打印小時工的姓名voidHourlyWorker::print()const{cout<<"\nHourlyWorker:"<<getFirstName()<<''<<getLastName();}2.9.2動態聯編動態聯編是指在系統運行時確定對象的類型,動態聯編提供了很大的便利性,有時也稱為后期聯編。

在面向對象方法中,動態聯編是和多態性及繼承性緊密相關的。因為,過程具有多態引用機制主要是依賴于引用的動態類型。例2-9給出了使用上一節所定義的類的測試程序。我們將以此例來討論靜態聯編和動態聯編。程序首先將ptr聲明為基類指針類型Employee*。Main()函數中的四小段代碼是類似的,因此我們只討論處理Boss對象的第一段代碼。例2-9

根據員工類型計算工資的程序

//Example2-9.cpp//類Employee層次結構的測試程序

#include<iostream.h>#include<iomanip.h>#include"empoly2.h"#include"boss.h"#include"commis1.h"#include"piece1.h"#include"hourly1.h“main(){//設置輸出格式cout<<setiosflags(ios::showpoint)<<setprecision(2);Employee*ptr;//基類指針Bossb("John","Smith",800.00);ptr=&b;//指向派生類對象的基類指針ptr->print();//動態聯編cout<<"earned$"<<ptr->earnings();//動態聯編b.print();//靜態聯編cout<<“earned$”<<b.earnings();//靜態聯編CommissionWorkerc("Sue","Jones",200.0,3.0,150);ptr=&c;//指向派生類對象的基類指針ptr->print();//動態聯編cout<<"earned$"<<ptr->earnings();//動態聯編c.print();//靜態聯編cout<<"earned$"<<c.earnings();//靜態聯編PieceWorkerp("Bob","Lewis",2.5,2

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論