版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
學校在冊人員
學生
教職工第八章繼承與多態(tài)本科生研究生碩士生博士生教師
行政人員
工人學校在學生教職工第八章繼承與多態(tài)本科生研究生碩【例8.1】由在冊人員類公有派生學生類classPerson{ stringIdPerson; //身份證號,18位數字
stringName; //姓名
boolSex; //性別
int Birthday; //格式1986年8月18日寫作19860818 stringHomeAddress; //家庭地址public: Person(string,string,Tsex,int,string);//構造函數
Person(); //默認的構造函數
~Person(); //析構函數【例8.1】由在冊人員類公有派生學生類public:【例8.1】由在冊人員類公有派生學生類voidSetName(string);
//修改名字
stringGetName(){returnName;}
//提取名字
voidSetSex(Tsexsex){Sex=sex;} //修改性別
boolGetSex(){returnSex;} //提取性別
voidSetId(stringid){IdPerson=id;} //修改身份證號
stringGetId(){returnIdPerson;}
//提取身份證號
voidSetBirth(intbirthday){Birthday=birthday;}//修改生日
int GetBirth(){returnBirthday;}//提取生日
voidSetHomeAdd(string); //修改住址
stringGetHomeAdd(){returnHomeAddress;}
//提取住址
voidPrintInfo(); //打印個人信息};//接口函數:【例8.1】由在冊人員類公有派生學生類voidSetN繼承(inheritance):該機制是面向對象程序設計使代碼可以復用的最重要的手段,它允許程序員在保持原有類特性的基礎上進行擴展,增加功能。這樣產生新的類,稱派生類。繼承呈現(xiàn)了面向對象程序設計的層次結構。體現(xiàn)了由簡單到復雜的認識過程。第八章繼承與多態(tài)多態(tài)性(polymorphism):多態(tài)性是考慮在不同層次的類中,以及在同一類中,同名的成員函數之間的關系問題。函數的重載,運算符的重載,屬于編譯時的多態(tài)性。以虛函數為基礎的運行時的多態(tài)性是面向對象程序設計的標志性特征。體現(xiàn)了類推和比喻的思想方法。
繼承(inheritance):第八章繼承與多態(tài)多態(tài)性【例8.1】由在冊人員類公有派生學生類classPerson{ stringIdPerson; //身份證號,18位數字
stringName; //姓名
boolSex; //性別
int Birthday; //格式1986年8月18日寫作19860818 stringHomeAddress; //家庭地址public: Person(string,string,Tsex,int,string);//構造函數
Person(); //默認的構造函數
~Person(); //析構函數【例8.1】由在冊人員類公有派生學生類public:【例8.1】由在冊人員類公有派生學生類voidSetName(string);
//修改名字
stringGetName(){returnName;}
//提取名字
voidSetSex(Tsexsex){Sex=sex;} //修改性別
TsexGetSex(){returnSex;} //提取性別
voidSetId(stringid){IdPerson=id;} //修改身份證號
stringGetId(){returnIdPerson;}
//提取身份證號
voidSetBirth(intbirthday){Birthday=birthday;}//修改生日
int GetBirth(){returnBirthday;}//提取生日
voidSetHomeAdd(string); //修改住址
stringGetHomeAdd(){returnHomeAddress;}
//提取住址
virtualvoidPrintInfo(); //打印個人信息};//接口函數:【例8.1】由在冊人員類公有派生學生類voidSetN【例8.1】由在冊人員類公有派生學生類派生的學生類:classStudent:publicPerson //定義派生的學生類{stringNoStudent;//學號
coursecs[30];//30門課程與成績public:Student(stringid,stringname,Tsexsex,intbirthday, stringhomeadd,stringnostud);
//注意派生類構造函數聲明方式
Student(); //默認派生類構造函數
~Student(); //派生類析構函數
SetCourse(string,int); //課程設置
intGetCourse(string); //查找成績
voidPrintInfo(); //打印學生情況};structcourse{stringcoursename;
intgrade;};【例8.1】由在冊人員類公有派生學生類派生的學生類:clas第八章繼承與多態(tài)8.1繼承與派生的概念
8.4虛基類(選讀)
8.3多重繼承與派生類成員標識
8.6多態(tài)性與虛函數
8.5派生類應用討論
8.2派生類的構造函數與析構函數
第八章繼承與多態(tài)8.1繼承與派生的概念8.1
繼承與派生的概念
層次概念是計算機的重要概念。通過繼承(inheritance)的機制可對類(class)分層,提供類型/子類型的關系。
C++通過類派生(classderivation)的機制來支持繼承。被繼承的類稱為基類(baseclass)或超類(superclass),新的類為派生類(derivedclass)或子類(subclass)。基類和派生類的集合稱作類繼承層次結構(hierarchy)。
如果基類和派生類共享相同的公有接口,則派生類被稱作基類的子類型(subtype)。
層次概念:派生反映了事物之間的聯(lián)系,事物的共性與個性之間的關系。派生與獨立設計若干相關的類,前者工作量少,重復的部分可以從基類繼承來,不需要單獨編程。8.1繼承與派生的概念層次概念是計算機的重要概念。通8.1
繼承與派生的概念8.1.1類的派生與繼承
8.1.2公有派生與私有派生
8.1繼承與派生的概念8.1.1類的派生與繼承【例8.1】由在冊人員類公有派生學生類派生的學生類:classStudent:publicPerson //定義派生的學生類{stringNoStudent;//學號
coursecs[30];//30門課程與成績public:Student(stringid,stringname,Tsexsex,intbirthday, stringhomeadd,stringnostud);
//注意派生類構造函數聲明方式
Student(); //默認派生類構造函數
~Student(); //派生類析構函數
SetCourse(string,int); //課程設置
intGetCourse(string); //查找成績
voidPrintInfo(); //打印學生情況};structcourse{stringcoursename;
intgrade;};【例8.1】由在冊人員類公有派生學生類派生的學生類:clas派生類的定義:class派生類名:訪問限定符基類名1《,訪問限定符基類名2,……,訪問限定符基類名n》{《《private:》
成員表1;》
//派生類增加或替代的私有成員《public:
成員表2;》
//派生類增加或替代的公有成員《protected:
成員表3;》
//派生類增加或替代的保護成員};//分號不可少其中基類1,基類2,……是已聲明的類。在派生類定義的類體中給出的成員稱為派生類成員,它們是新增加成員,它們給派生類添加了不同于基類的新的屬性和功能。派生類成員也包括取代基類成員的更新成員。8.1.1類的派生與繼承派生類的定義:8.1.1類的派生與繼承基類1基類2……基類n派生類1派生類2基類派生類1派生類2(a)多重繼承
(b)單繼承
圖8.1多重繼承與單繼承
一個基類可以直接派生出多個派生類
派生類可以由多個基類共同派生出來,稱多重繼承。8.1.1類的派生與繼承多重繼承:如果一個派生類可以同時有多個基類,稱為多重繼承(multiple-inheritance),這時的派生類同時得到了多個已有類的特征。單繼承:派生類只有一個直接基類的情況稱為單繼承(single-inheritance)?;?基類2……基類n派生類1派生類2基類派生類1派生類2(8.1.1類的派生與繼承
在派生過程中,派生出來的新類同樣可以作為基類再繼續(xù)派生出更新的類,依此類推形成一個層次結構。直接參與派生出某類稱為直接基類,而基類的基類,以及更深層的基類稱為間接基類。類族:
同時一個基類可以直接派生出多個派生類。這樣形成了一個相互關聯(lián)的類族。如MFC就是這樣的族類,它由一個CObject類派生出200個MFC類中的絕大多數。多層次繼承:8.1.1類的派生與繼承在派生過程中,派編制派生類時可分四步
吸收基類的成員
改造基類成員
發(fā)展新成員
重寫構造函數與析構函數
8.1.1類的派生與繼承不論是數據成員,還是函數成員,除構造函數與析構函數外全盤接收
聲明一個和某基類成員同名的新成員,派生類中的新成員就屏蔽了基類同名成員稱為同名覆蓋(override)派生類新成員必須與基類成員不同名,它的加入保證派生類在功能上有所發(fā)展。派生編程步驟:編制派生類時可分四步吸收基類的成員改造基類成員發(fā)展新成8.1.1類的派生與繼承第二步中,新成員如是成員函數,參數表也必須一樣,否則是重載。第三步中,獨有的新成員才是繼承與派生的核心特征。第四步是重寫構造函數與析構函數,派生類不繼承這兩種函數。不管原來的函數是否可用一律重寫可免出錯(?????)。訪問控制:亦稱為繼承方式,是對基類成員進一步的限制。訪問控制也是三種:公有(public)方式,亦稱公有繼承保護(protected)方式,亦稱保護繼承私有(private)方式,亦稱私有繼承。
8.1.1類的派生與繼承第二步中,新成員如是成員函數,參8.1.2公有派生與私有派生訪問限定符兩方面含義:派生類成員(新增成員)函數對基類(繼承來的)成員的訪問(調用和操作),和從派生類對象之外對派生類對象中的基類成員的訪問。classStudent:publicPerson{……public: Student(stringid,stringname,Tsexsex,int birthday,stringhomeadd,stringnostud);};classPerson{private:
stringName; //姓名public: stringGetName(){returnName;} //提取名字 ……}
8.1.2公有派生與私有派生訪問限定符兩方面含義:派生類成8.1.2公有派生與私有派生不可直接訪問
不可直接訪問
private不可直接訪問
privateprotected不可直接訪問
privatepublic私有派生
不可直接訪問
不可直接訪問
private不可直接訪問
protectedprotected可直接訪問
publicpublic公有派生
在派生類對象外訪問派生類對象的基類成員
在派生類中對基類成員的訪問限定
基類中的訪問限定
派生方式
公有派生是絕對主流。8.1.2公有派生與私有派生不可直接訪問不可直接訪問p【例8.1】由在冊人員類公有派生學生類//注意Person參數表不用類型Student::Student(stringid,stringname,Tsexsex,intbirthday,stringhomeadd,stringnostud):Person(id,name,sex,birthday,homeadd){ NoStudent=nostud;
for(inti=0;i<30;i++) //課程與成績清空
{ cs[i].coursename="#"; cs[i].grade=0; }}派生類構造函數:【例8.1】由在冊人員類公有派生學生類//注意Person參派生類構造函數的定義:派生類名::派生類名(參數總表):基類名1(參數名表1)《,基類名2(參數名表2),……,基類名n(參數名表n)》,《成員對象名1(成員對象參數名表1),……,成員對象名m(成員對象參數名表m)》{…… //派生類新增成員的初始化;} //所列出的成員對象名全部為新增成員對象的名字注意:在構造函數的聲明中,冒號及冒號以后部分必須略去。
所謂不能繼承并不是不能利用,而是把基類的構造函數作為新的構造函數的一部分,或者講調用基類的構造函數?;惷麅H指直接基類,寫了底層基類,編譯器認為出錯。
冒號后的基類名,成員對象名的次序可以隨意,這里的次序與調用次序無關。
8.2派生類的構造函數與析構函數派生類構造函數的定義:注意:8.2派生類的構造函數與析構派生類構造函數各部分執(zhí)行次序:
1.調用基類構造函數,按它們在派生類定義的先后順序,順序調用。
2.調用成員對象的構造函數,按它們在類定義中聲明的先后順序,順序調用。3.派生類的構造函數體中的操作。8.2派生類的構造函數與析構函數注意:在派生類構造函數中,只要基類不是使用無參的默認構造函數都要顯式給出基類名和參數表。如果基類沒有定義構造函數,則派生類也可以不定義,全部采用系統(tǒng)給定的默認構造函數。如果基類定義了帶有形參表的構造函數時,派生類就應當定義構造函數。派生類構造函數各部分執(zhí)行次序:8.2派生類的構造函數與8.2派生類的構造函數與析構函數析構函數:析構函數的功能是作善后工作。
只要在函數體內把派生類新增一般成員處理好就可以了,而對新增的成員對象和基類的善后工作,系統(tǒng)會自己調用成員對象和基類的析構函數來完成。析構函數各部分執(zhí)行次序與構造函數相反,首先對派生類新增一般成員析構,然后對新增對象成員析構,最后對基類成員析構。8.2派生類的構造函數與析構函數析構函數:【例8.1】由在冊人員類公有派生學生類【例8.1】由在冊人員類公有派生學生類。我們希望基類和派生類共享相同的公有接口,只能采用公有派生來實現(xiàn)?;悾篹num
Tsex{mid,man,woman};
classPerson{ stringIdPerson; //身份證號,18位數字
stringName; //姓名
TsexSex; //性別
intBirthday; //格式1986年8月18日寫作19860818 stringHomeAddress; //家庭地址public: Person(string,string,Tsex,int,string); //構造函數
Person(); //默認的構造函數
~Person(); //析構函數【例8.1】由在冊人員類公有派生學生類【例8.1】由在冊人員【例8.1】由在冊人員類公有派生學生類
voidSetName(string);
//修改名字
stringGetName(){returnName;}
//提取名字
voidSetSex(Tsexsex){Sex=sex;} //修改性別
TsexGetSex(){returnSex;} //提取性別
voidSetId(stringid){IdPerson=id;} //修改身份證號
stringGetId(){returnIdPerson;}
//提取身份證號
voidSetBirth(intbirthday){Birthday=birthday;}//修改生日
intGetBirth(){returnBirthday;}//提取生日
voidSetHomeAdd(string); //修改住址
stringGetHomeAdd(){returnHomeAddress;}
//提取住址
virtualvoidPrintInfo(); //輸出個人信息
};//接口函數:【例8.1】由在冊人員類公有派生學生類 voidSetN【例8.1】由在冊人員類公有派生學生類派生的學生類:classStudent:publicPerson //定義派生的學生類{stringNoStudent; //學號
coursecs[30]; //30門課程與成績public:Student(stringid,stringname,Tsexsex,intbirthday, stringhomeadd,stringnostud);
//注意派生類構造函數聲明方式
Student(); //默認派生類構造函數
~Student(); //派生類析構函數
SetCourse(string,int); //課程設置
intGetCourse(string); //查找成績
voidPrintInfo(); //打印學生情況};structcourse{stringcoursename;
intgrade;};驗證主函數【例8.1】由在冊人員類公有派生學生類派生的學生類:clas8.2派生類的構造函數與析構函數注意:本例中標準C++字符串string是作為成員對象使用的(聚合),動態(tài)內存分配的構造和析構被封裝起來,使用十分簡單。如使用動態(tài)生成的C風格字符串,要考慮深復制,那要復雜得多。
提倡完善的類對象封裝,不僅封裝數據和對數據的操作,而且封裝資源的動態(tài)分配與釋放,形成一個完備的子系統(tǒng)。在一個有層次結構的類體系中資源的動態(tài)分配與釋放應封裝在成員對象中,如同使用標準的string字符串類那樣。聚合是一種完善的封裝。采用成員對象將大大簡化層次結構的類體系中資源的動態(tài)分配與釋放的處理方法,不再出現(xiàn)難度極大的多層次的深復制。8.2派生類的構造函數與析構函數注意:8.3多重繼承與派生類成員標識(選讀)由多個基類共同派生出新的派生類,這樣的繼承結構被稱為多重繼承或多繼承(multiple-inheritance)
椅子床沙發(fā)(單繼承)躺椅(多重繼承)兩用沙發(fā)(多重繼承)圖8.2椅子,床到兩用沙發(fā)多重繼承實例:8.3多重繼承與派生類成員標識(選讀)由多個基類共同派生在冊人員學生(單繼承)教職工(單繼承)兼職教師(單繼承)教師(單繼承)行政人員(單繼承)工人(單繼承)研究生(單繼承)行政人員兼教師(多重繼承)在職研究生(多重繼承)研究生助教(多重繼承)圖8.3大學在冊人員繼承關系8.3多重繼承與派生類成員標識(選讀)派生出來的新類同樣可以作為基類再繼續(xù)派生出更新的類,依此類推形成一個層次結構。
在冊人員學生(單繼承)教職工(單繼承)兼職教師(單繼承)教師8.3多重繼承與派生類成員標識(選讀)歧義性問題:參見圖8.3,比如行政人員兼教師,在其基類教師中有一個“教職工編號”,另一基類行政人員中也有一個“教職工編號”,如果只講教職工編號那么是哪一個基類中的呢?這兩者可能是一回事,但計算機系統(tǒng)并不這么認為。進一步,如果“教職工編號”是由兩個基類“教師”和“行政人員”共同的基類“教職工”類繼承來的,只有同一個標識符,也不能用改標識符來區(qū)分。
唯一標識問題:通常采用作用域分辨符“::”:基類名::成員名;//數據成員基類名::成員名(參數表);//函數成員
8.3多重繼承與派生類成員標識(選讀)歧義性問題:唯一標classEGStudent
intNo在職學號………classGStudentintNo研究生號
……….classStudentintNo學生號
……….
classPersonintNo身份證號
……….classEmployeeintNo工作證號
……….classPersonintNo身份證號
……….圖8.4(a)在職研究生派生類關系
定義EGStudent類對象EGStudent1,并假定派生全部為公有派生,而intNo全為公有成員:EGStud1.No//在職學號EGStud1.GStudent::No//研究生號EGStud1.GStudent.Student::No
//學生號
EGStud1.GStudent.Student.Person::No//身份證號EGStud1.Employee::No//工作證號EGStud1.Employee.Person::No
//身份證號兩個身份證號從邏輯上講應是一回事,但是物理上是分配了不同內存空間,是兩個變量,請參見圖8.4(b)。classEGStudentintNoclassGSPerson
Person
StudentEmployeeGStudent
EGStudentPerson成員
Person成員
Student新成員
GStudent新成員
Employee新成員
EGStudent新成員
圖8.4(b)在職研究生派生類存儲圖
建議采用有確定字面意思的標識符,它可以被編譯器簡單區(qū)分出來。如果classPerson的身份證號標識為intIdPerson,則寫為:EGStud1.GStudent::IdPersonEGStud1.Employee::IdPerson不必標出那么多層次的類,但寫EGStud1.IdPerson是錯的。
作用域分辨符不能嵌套使用,如:EGStud1.GStudent::Student::No //學生號EGStud1.GStudent::Student::Person::No //身份證號是錯誤的。8.3多重繼承與派生類成員標識(選讀)PersonPersonStudentEmployee8.3多重繼承與派生類成員標識(選讀)一般數據成員總是私有成員,派生類對基類的訪問只能間接進行。訪問身份證號,應通過classPerson中的公有成員函數(接口)GetNo()和SetNo()進行:EGStud1.Employee.Person::SetNo(intno);no=EGStud1.Employee.Person::GetNo();注意:8.3多重繼承與派生類成員標識(選讀)一般數據成員總是私【例8.2】由圓和高多重繼承派生出圓錐。因為公有派生時,在派生類中不可以直接訪問基類的私有成員,但可以直接訪問基類的保護成員,當需要在派生類中訪問基類的數據成員時,可以將它們定義為保護的,而不是私有的。
本例中類Circle為圓;類Line為高;類Cone為圓錐,由Circle和Line公有派生而來。在Cone類中,Circle和Line類的接口完全不變,可以直接調用,這就是公有派生的優(yōu)點。在Cone的成員函數中可直接訪問Circle和Line中的公有成員和保護成員。
【例8.2】由圓和高多重繼承派生出圓錐檢證主程序:圓類Circle定義高類Line定義圓錐類Cone定義【例8.2】由圓和高多重繼承派生出圓錐。【例8.2】由圓和高虛基類的引入:在圖8.4中,兩個身份證號顯然是不合理的??梢园裞lassPerson這個共同基類設置為虛基類,這樣就僅有一個Person基類成員,從不同路徑繼承來的同名數據成員(身份證號)在內存中就是同一個數據。8.4虛基類(選讀)注意:virtual關鍵字只對緊隨其后的基類名起作用:classStudent:virtual
publicPerson{...};classEmployee:virtual
publicPerson{...};虛基類(virtualbaseclass)定義:class
派生類名:virtual訪問限定符基類類名{...};class
派生類名:訪問限定符
virtual基類類名{...};虛基類的引入:8.4虛基類(選讀)注意:虛基類(virt8.4虛基類(選讀)圖8.5采用虛基類后在職研究生類儲存圖StudentGStudentEGStudentPersonStudent新成員GStudent新成員PersonEmployee新成員Person成員EGStudent新成員PersonPersonEmployee這種繼承稱為虛擬繼承虛擬繼承:在Person的位置上放的是指針,兩個指針都指向Person成員存儲的內存。這種繼承稱為虛擬繼承(virtualinheritance)。8.4虛基類(選讀)圖8.5采用虛基類后在職研究生類8.4虛基類(選讀)派生類名::派生類名(參數總表):基類名1(參數名表1)《,基類名2(參數名表2),……,基類名n(參數名表n)》,《成員對象名1(成員對象參數名表1),……,成員對象名m(成員對象參數名表m)》,底層虛基類名1(參數名表1)《,……,底層虛基類名r(參數名表r)》{……//派生類新增成員的初始化};//所列出的成員對象名全部為新增成員對象的名字在多層虛擬繼承構造函數中,基類名不僅要列出直接基類,而且要列出底層虛基類,否則編譯器認為出錯。虛擬繼承的構造函數:8.4虛基類(選讀)派生類名::派生類名(參數總表):基8.4虛基類(選讀)在派生類對象的創(chuàng)建中:首先是虛基類的構造函數并按它們聲明的順序構造。第二批是非虛基類的構造函數按它們聲明的順序調用。第三批是成員對象的構造函數。最后是派生類自己的構造函數被調用。構造函數執(zhí)行次序:8.4虛基類(選讀)在派生類對象的創(chuàng)建中:構造函數執(zhí)行次8.4虛基類(選讀)【例8.3】在采用虛基類的多重繼承中,構造與析構的次序。classDclass:publicBclass1,virtualBclass3,virtualBclass2{Objectobject;public:Dclass():object(),Bclass2(),Bclass3(),Bclass1(){cout<<"派生類建立!\n";}~Dclass(){cout<<"派生類析構!\n";}};voidmain(){ Dclassdd;cout<<"主程序運行!\n";}8.4虛基類(選讀)【例8.3】在采用虛基類的多重繼承中運行結果:ConstructorBclass3 //第一個虛擬基類,與派生類析構函數排列無關ConstructorBclass2 //第二個虛擬基類ConstructorBclass1 //非虛擬基類ConstructorObject //對象成員派生類建立!主程序運行!派生類析構!deconstructorObject //析構次序相反deconstructorBclass1deconstructorBclass2deconstructorBclass3//析構的次序與構造的次序相反。8.4虛基類(選讀)運行結果:8.4虛基類(選讀)對照圖8.5,盡管Employee和Student的構造函數都包含Person的構造函數,但并未真正調用。唯一的一次調用是在EGStudent構造函數中。如是非虛基類,則有兩次調用。8.4虛基類(選讀)【例8.4】虛基類在多層多重繼承中的應用
——在職研究生類定義。以虛基類定義公有派生的學生類以虛基類定義公有派生的研究生類以虛基類定義公有派生的教職工類多重繼承的以虛基類定義公有派生的在職研究生類對照圖8.5,盡管Employee和Student的構造函數一、派生類與基類:
在任何需要基類對象的地方都可以用公有派生類的對象來代替,這條規(guī)則稱賦值兼容規(guī)則。它包括以下情況:8.5派生類應用討論1.
派生類的對象可以賦值給基類的對象,這時是把派生類對象中從對應基類中繼承來的成員賦值給基類對象。反過來不行,因為派生類的新成員無值可賦。2.
可以將一個派生類的對象的地址賦給其基類的指針變量,但只能通過這個指針訪問派生類中由基類繼承來的成員,不能訪問派生類中的新成員。同樣也不能反過來做。3.
派生類對象可以初始化基類的引用。引用是別名,但這個別名只能包含派生類對象中的由基類繼承來的成員?!纠?.5】為例8.1定義復制函數,實現(xiàn)深復制。一、派生類與基類:8.5派生類應用討論1.派生類的二、繼承與聚合
繼承使派生類可以利用基類的成員,如果我們把基類的對象作為一個新類的對象成員,也可以取得類似的效果。派生類采用繼承方法,成員對象是聚合的概念?;愒谂缮愔兄荒芾^承一個(間接基類不在討論之中)不能同時安排兩個,否則成員名即使使用域分辨符也會發(fā)生沖突:classA{public:intK;...};classB:publicA,publicA{...};兩個A無論如何無法分辨出來。如果要用兩個A只能采用成員對象。
更深入地探討后會發(fā)現(xiàn):成員對象體現(xiàn)了封裝更深層次的含義。在派生類和它的基類中是不應該有內存的動態(tài)分配的,動態(tài)分配的部分應該封裝在成員對象中,在該成員對象的析構函數中釋放內存,在該成員對象中提供深復制。類string就是如此。它的內部就是一個完備的小系統(tǒng)。這樣程序員就可以放心地使用它,而不需要為它做任何事情。8.5派生類應用討論二、繼承與聚合8.5派生類應用討論8.6多態(tài)性與虛函數問題:如何用同一個函數(同名函數)求三角形、長方形、梯形等不同基本形狀的面積?8.6多態(tài)性與虛函數問題:8.6多態(tài)性與虛函數多態(tài)性:多態(tài)性是面向對象程序設計的關鍵技術之一。若程序設計語言不支持多態(tài)性,不能稱為面向對象的語言。利用多態(tài)性技術,可以調用同一個函數名的函數,實現(xiàn)完全不同的功能。在C++中有兩種多態(tài)性
編譯時的多態(tài)性
運行時的多態(tài)性
運行時的多態(tài)性是指在程序執(zhí)行前,無法根據函數名和參數來確定該調用哪一個函數,必須在程序執(zhí)行過程中,根據執(zhí)行的具體情況來動態(tài)地確定。它是通過類繼承關系和虛函數來實現(xiàn)的。目的也是建立一種通用的程序。通用性是程序追求的主要目標之一。
通過函數的重載和運算符的重載來實現(xiàn)的。8.6多態(tài)性與虛函數多態(tài)性:在C++中有兩種多態(tài)性編譯8.6多態(tài)性與虛函數8.6.1虛函數的定義
8.6.4動態(tài)綁定
(選讀)
8.6.2純虛函數
8.6.3
繼承與多態(tài)的應用——單鏈表派生類(選讀)
8.6多態(tài)性與虛函數8.6.1虛函數的定義8.6.1虛函數的定義虛函數的概念:虛函數是一個類的成員函數,定義格式如下:virtual返回類型函數名(參數表);關鍵字virtual指明該成員函數為虛函數。virtual僅用于類定義中,如虛函數在類外定義,不可再加virtual。當一個類的某個成員函數被定義為虛函數,則由該類派生出來的所有派生類中,該函數始終保持虛函數的特征。8.6.1虛函數的定義虛函數的概念:當一個類的某個成員函8.6.1虛函數的定義當在派生類中重新定義虛函數(overridingavirtualfunction,亦譯作超載或覆蓋)時,不必加關鍵字virtual。但重新定義時不僅要同名,而且它的參數表和返回類型全部與基類中的虛函數一樣,否則出錯。虛函數與在8.1.1節(jié)中介紹的派生類的第二步——改造類成員,同名覆蓋(override)有關:如未加關鍵字virtual,則是普通的派生類中的新成員函數覆蓋基類同名成員函數(當然參數表必須一樣,否則是重載),可稱為同名覆蓋函數,它不能實現(xiàn)運行時的多態(tài)性。
虛函數定義要點:8.6.1虛函數的定義當在派生類中重新定義虛函數(ove虛函數與運行時的多態(tài)性:【例8.6】計算學分??捎杀究粕惻缮鲅芯可悾鼈兏髯缘膹恼n程學時數折算為學分數的算法是不同的,本科生是16個學時一學分,而研究生是20個學時一學分。8.6.1虛函數的定義【例8.7】計算學分。派生類定義不再重復。虛函數與運行時的多態(tài)性:8.6.1虛函數的定義【例8.7成員函數設置為虛函數的要點:1.派生類中定義虛函數必須與基類中的虛函數同名外,還必須同參數表,同返回類型。否則被認為是重載,而不是虛函數。如基類中返回基類指針,派生類中返回派生類指針是允許的,這是一個例外。2.只有類的成員函數才能說明為虛函數。這是因為虛函數僅適用于有繼承關系的類對象。3.靜態(tài)成員函數,是所有同一類對象共有,不受限于某個對象,不能作為虛函數。4.實現(xiàn)動態(tài)多態(tài)性時,必須使用基類類型的指針變量或引用,使該指針指向該基類的不同派生類的對象,并通過該指針指向虛函數,才能實現(xiàn)動態(tài)的多態(tài)性。8.6.1虛函數的定義成員函數設置為虛函數的要點:1.派生類中定義虛函數必須與5.內聯(lián)函數因為每個對象有獨立的一份函數代碼,無映射關系,不能作為虛函數。6.析構函數可定義為虛函數,構造函數不能定義虛函數,因為在調用構造函數時對象還沒有完成實例化。在基類中及其派生類中都動態(tài)分配的內存空間時,必須把析構函數定義為虛函數,實現(xiàn)撤消對象時的多態(tài)性。7.函數執(zhí)行速度要稍慢一些。為了實現(xiàn)多態(tài)性,每一個派生類中均要保存相應虛函數的入口地址表,函數的調用機制也是間接實現(xiàn)。所以多態(tài)性總是要付出一定代價,但通用性是一個更高的目標。8.如果定義放在類外,virtual只能加在函數聲明前面,不能(再)加在函數定義前面。正確的定義必須不包括virtual。8.6.1虛函數的定義5.內聯(lián)函數因為每個對象有獨立的一份函數代碼,無映射關系,不8.6.1虛函數的定義【例8.5_1】根據賦值兼容規(guī)則可以用基類的指針指向派生類對象,如果由該指針撤銷派生類對象,則必須將析構函數說明為虛函數,實現(xiàn)多態(tài)性,自動調用派生類析構函數。通常要求將類設計成通用的,無論其他程序員怎樣調用都必須保證不出錯,所以必須把析構函數定義為虛函數。下面把【例8.5】析構函數改造為虛函數classPerson{
//數據成員略public:
virtual~Person();
//只需在此聲明一次,派生類的析構函數全為虛函數
};
//其他成員函數略Person::~Person(){cout<<"Person析構函數"<<endl;}8.6.1虛函數的定義【例8.5_1】根據賦值兼容規(guī)則可Person*per4;Student*stu4=newStudent;//動態(tài)建立對象*stu4*stu4=stu1;//把stu1的數據拷入*stu4stu4->PrintStudentInfo();per4=stu4;deleteper4;//用基類指針撤銷派生類,動態(tài)生成的對象必須顯式撤銷8.6.1虛函數的定義在主函數中添加以下內容:
通過在析構函數中加顯示語句發(fā)現(xiàn)先調Student析構函數,后調Person析構函數。這里再次強調動態(tài)生成的對象必須顯式撤銷。Person*per4;8.6.1虛函數的定義在主函數純虛函數:純虛函數(purevirtualfunction)是指被標明為不具體實現(xiàn)的虛擬成員函數。它用于這樣的情況:定義一個基類時,會遇到無法定義基類中虛函數的具體實現(xiàn),其實現(xiàn)依賴于不同的派生類。8.6.2純虛函數純虛函數的定義:virtual返回類型函數名(參數表)=0;含有純虛函數的基類是不能用來定義對象的。純虛函數沒有實現(xiàn)部分,不能產生對象,所以含有純虛函數的類是抽象類。純虛函數:8.6.2純虛函數純虛函數的定義:含有純虛函數1定義純虛函數時,不能定義虛函數的實現(xiàn)部分。即使是函數體為空也不可以,函數體為空就可以執(zhí)行,只是什么也不做就返回。而純虛函數不能調用。2“=0”表明程序員將不定義該函數,函數聲明是為派生類保留一個位置?!?0”本質上是將指向函數體的指針定為NULL。3在派生類中必須有重新定義的純虛函數的函數體,這樣的派生類才能用來定義對象。8.6.2純虛函數定義純虛函數的要點:1定義純虛函數時,不能定義虛函數的實現(xiàn)部分。即使是函數體【例8.8】學校對在冊人員進行獎勵,依據是業(yè)績分,但是業(yè)績分的計算方法只能對具體人員進行,如學生,教師,行政人員,工人,算法都不同,所以可以將在冊人員類作為一個抽象類,業(yè)績計算方法作為一個純虛函數。在主函數中全部用指向基類的指針來調用8.6.2純虛函數業(yè)績分基類定義業(yè)績分學生派生類定義業(yè)績分教師派生類定義驗證主函數【例8.8】學校對在冊人員進行獎勵,依據是業(yè)績分,但是業(yè)績分【例8.9】用虛函數來實現(xiàn)辛普生法求函數的定積分。8.6.2純虛函數純虛函數實現(xiàn)通用算法:辛普生法求定積分類在派生類中加被積函數:驗證主函數【例8.9】用虛函數來實現(xiàn)辛普生法求函數的定積分。8.6.28.6.3繼承與多態(tài)的應用——單鏈表派生類(選讀)【例8.10】通用單鏈表派生類。第一步改造【例7.4】的頭文件,不采用模板類,而采用虛函數實現(xiàn)多態(tài)性,達到通用的目的。結點類數據域被改造為指針,而把數據放在一個抽象類中,由指針與之建立聯(lián)系。數據域(指向抽象數據類的指針)由抽象類派生的數據類對象(如串對象)指針域(指向下一結點)結點類對象動態(tài)建立的數據類對象圖8.9結點構造8.6.3繼承與多態(tài)的應用——單鏈表派生類(選讀)【例8classObject{//數據類為抽象類public:Object(){}virtualbooloperator>(Object&)=0;
//純虛函數,參數必須為引用或指針
virtualbooloperator!=(Object&)=0;
//純虛函數,參數必須為引用或指針
virtualvoidPrint()=0;//純虛函數
virtual~Object(){}};//析構函數可為虛函數,構造函數不行8.6.3繼承與多態(tài)的應用——單鏈表派生類(選讀)結點組織,采用結點類加數據類數據類定義:本題要點:采用虛函數實現(xiàn)多態(tài)性,達到通用的目的。堆內存的分配與釋放,關鍵不是創(chuàng)建,而是釋放!classObject{//數據類為抽象類說明:數據抽象類中含有三個純虛函數:輸出函數和兩個比較函數。當抽象類在派生時重新定義三個純虛函數,可以進行各種類型,包括類和結構對象的比較和輸出。本例介紹程序總體組成為主,鏈表的操作由學生自己仔細閱讀。8.6.3繼承與多態(tài)的應用——單鏈表派生類(選讀)抽象類中的析構函數也是虛函數,這一點非常重要,當抽象類派生的數據類的數據部分是動態(tài)產生的,而由結點類刪除釋放數據類對象時,必須由數據類的析構函數來釋放該類對象數據部分占用的動態(tài)分配的內存。這時必須重新定義析構函數。說明:本例介紹程序總體組成為主,鏈表的操作由學生自己仔細閱讀ClassNode{Object*info;//數據域用指針指向數據類對象
Node*link;//指針域public:Node();//生成頭結點的構造函數
~Node();//析構函數
voidInsertAfter(Node*P);//在當前結點后插入一個結點
Node*RemoveAfter();
//刪除當前結點的后繼結點,返回該結點備用
voidLinkinfo(Object*obj);//把數據對象連接到結點
friendclassList;
//以List為友元類,List可直接訪問Node的私有函數,};8.6.3繼承與多態(tài)的應用——單鏈表派生類(選讀)結點類定義:ClassNode{8.6.3繼承與多態(tài)的應用——單鏈classList{Node*head,*tail;//鏈表頭指針和尾指針public:List();//構造函數,生成頭結點(空鏈表)~List();//析構函數
voidMakeEmpty();//清空鏈表,只余表頭結點
Node*Find(Object&obj);
//搜索數據域與定值相同的結點,返回該結點的地址
intLength();//計算單鏈表長度
voidPrintList();//打印鏈表的數據域
voidInsertFront(Node*p);//可用來向前生成鏈表
voidInsertRear(Node*p);//可用來向后生成鏈表
voidInsertOrder(Node*p);//按升序生成鏈表
Node*CreatNode();//創(chuàng)建一個結點(孤立結點)Node*DeleteNode(Node*p);};//刪除指定結點8.6.3繼承與多態(tài)的應用——單鏈表派生類(選讀)鏈表類定義:classList{8.6.3繼承與多態(tài)的應用——單鏈第二步,取代模板定義泛型類型為具體類型(包括類)的步驟是由抽象類派生數據類。數據類的數據采用字符類串string,動態(tài)分配和釋放內存都在string類中完成。為了完成數據類的比較和輸出,超載了比較運算符和輸出函數(虛函數)。數據類的比較實際是字符串string的比較。8.6.3繼承與多態(tài)的應用——單鏈表派生類(選讀)classStringObject:publicObject{stringsptr;public:StringObject(){sptr="";}StringObject(strings){sptr=s;}~StringObject();//析構函數
booloperator>(Object&);//大于函數
booloperator!=(Object&);//不等于函數
voidPrint();//打印函數};驗證主函數運行結果第二步,取代模板定義泛型類型為具體類型(包括類)的步驟是由抽分析與比較:
在該程序中,特別要仔細揣摩堆內存的分配與釋放。刪除一個結點時系統(tǒng)自動調用結點類析構函數釋放結點占用的動態(tài)內存,而結點類析構函數自動調用數據域類虛析構函數,數據域類析構函數自動調用string類的析構函數釋放所占用的動態(tài)內存。一環(huán)套一環(huán),一步都不能錯。這是使用動態(tài)內存分配的關鍵。即關鍵不是創(chuàng)建,而是釋放!
運行時的多態(tài)性需要維護一個動態(tài)指針表才能正確指向各相關類中的同名虛函數。所以多態(tài)與模板比較,模板的效率更高,標準模板庫中用容器來泛型化數據結構中的許多算法。對數據結構的使用當然借助模板庫。多態(tài)不適用于性能要求很高的實時應用程序,但繼承與多態(tài)可用與其它更多方面,每一種技術都有可以充分發(fā)揮自己能力的地方。8.6.3繼承與多態(tài)的應用——單鏈表派生類(選讀)分析與比較:8.6.3繼承與多態(tài)的應用——單鏈表派生類(動態(tài)綁定(dynamicbinding)亦稱滯后綁定(latebinding),對應于靜態(tài)綁定(staticbinding)。如果使用對象名和點成員選擇運算符“.”引用特定的一個對象來調用虛函數,則被調用的虛函數是在編譯時確定的(稱為靜態(tài)綁定)
如果使用基類指針或引用指明派生類對象并使用該指針調用虛函數(成員選擇符用箭頭號“->”),則程序動態(tài)地(運行時)選擇該派生類的虛函數,稱為動態(tài)綁定。8.6.4動態(tài)綁定(選讀)綁定是指計算機程序自身彼此關聯(lián)的過程,是把一個標識符名和一個存儲地址聯(lián)系在一起的過程,也就是把一條消息和一個對象的操作相結合的過程。動態(tài)綁定(dynamicbinding)亦稱滯后綁定(la圖8.9虛函數調用的控制流程“dog”StringObject動態(tài)無名對象StringObject動態(tài)無名對象“cat”指向Object類指針指向結點類指針指向Object類指針指向結點類指針指向Object類指針Λ指向結點類指針StringObject動態(tài)無名對象“cock”·
·
·析構函數指針0比較函數指針0輸出函數指針StringObject虛函數表抽象類Object虛函數表析構函數指針比較函數指針輸出函數指針ComplexObject虛函數
析構函數指針
比較函數指針
輸出函數指針·
·
·默認析構函數釋放動態(tài)串析構函數串比較函數打印串函數默認析構函數復數模大小比較函數打印復數函數圖8.9虛函數調用的控制流程“dog”StringObj8.6.4動態(tài)綁定(選讀)
C++編譯器編譯含有一個或幾個虛函數的類及其派生類時,對該類建立虛函數表(Virtualfunctiontable,vtable)。虛函數表使執(zhí)行程序正確選擇每次執(zhí)行時應使用的虛函數。多態(tài)是由復雜的數據結構實現(xiàn)的,參見圖8.10。圖8.10是以【例8.10】為基礎的,不過增加了一個由抽象類Object派生的復數數據類ComplexObject。圖中列出了基類和各派生類的虛函數表,這些表是由指向函數的指針組成的。
8.6.4動態(tài)綁定(選讀)C++編譯器編譯含有一個8.6.4動態(tài)綁定(選讀)
還有第二層指針,在實例化帶虛函數的類(創(chuàng)建對象)時,編譯器在對象前加上一個指向該類的虛函數表的指針。 第三層指針是鏈表結點類對象中指向抽象基類Object的指針(這也可以是引用,但本例是指針)。虛函數的調用是這樣進行的,考慮虛函數Compare(),則看含“cat”的結點。由該結點的info指針找到含“cat”的無名對象,再由對象前的指針找到StringObject虛函數表,移動4個字節(jié)(一個指針占4個字節(jié))找到比較函數指針,進入串比較函數。8.6.4動態(tài)綁定(選讀)還有第二層指針,在實例完第八章繼承與派生謝謝!完第八章繼承與派生謝謝!【例8.1】由在冊人員類公有派生學生類Person::Person(stringid,stringname,Tsexsex, intbirthday,stringhomeadd){ IdPerson=id; Name=name; Sex=sex; Birthday=birthday; HomeAddress=homeadd;}//作為一個管理程序,這個構造函數并無必要,因為數據總是另外輸入的。僅為說明語法存在。分析構造函數:【例8.1】由在冊人員類公有派生學生類Person::Per【例8.1】由在冊人員類公有派生學生類Person::Person(){ IdPerson="#";Name="#";Sex=mid; Birthday=0;HomeAddress="#";}分析默認的構造函數:分析析構函數:Person::~Person(){}//string內部動態(tài)數組的釋放,由string自帶的析構函數完成【例8.1】由在冊人員類公有派生學生類Person::Per【例8.1】由在冊人員類公有派生學生類voidPerson::SetName(stringname){ Name=name;//拷入新姓名}修改名字:voidPerson::SetHomeAdd(stringhomeadd){ HomeAddress=homeadd;}修改住址:【例8.1】由在冊人員類公有派生學生類voidPerson【例8.1】由在冊人員類公有派生學生類voidPerson::PrintInfo(){
inti; cout<<"身份證號:"<<IdPerson<<'\n'<<"姓名:"<<Name<<'\n'<<"性別:";
if(Sex==man)cout<<"男"<<'\n';
else
if(Sex==woman)cout<<"女"<<'\n';
elsecout<<""<<'\n'; cout<<"出生年月日:"; i=Birthday; cout<<i/10000<<"年"; i=i%10000; cout<<i/100<<"月"<<i%100<<"日"<<'\n‘<<"家庭住址:"<<HomeAddress<<'\n';}輸出個人信息:【例8.1】由在冊人員類公有派生學生類voidPerson【例8.1】由在冊人員類公有派生學生類Student::Student(stringid,stringname,Tsexsex,intbirthday,stringhomeadd,stringnostud):Person(id,name,sex,birthday,homeadd){ //注意Person參數表不用類型
NoStudent=nostud;
for(inti=0;i<30;i++) //課程與成績清空
{ cs[i].coursename="#"; cs[i].grade=0; }}派生類構造函數:【例8.1】由在冊人員類公有派生學生類Student::St【例8.1】由在冊人員類公有派生學生類Student::Student()//基類默認的無參數構造函數不必顯式給出{ inti;
NoStudent="";
for(i=0;i<30;i++)//課程與成績清零,將來由鍵盤輸入
{ cs[i].coursename=""; cs[i].grade=0; }}Student::~Student(){}
//基類析構函數以及成員對象析構函數自動調用默認派生類構造函數:派生類析構函數:【例8.1】由在冊人員類公有派生學生類Student::StintStudent::SetCourse(stringcoursename,intgrade){
boolb=false;//標識新輸入的課程,還是更新成績
inti;
for(i=0;i<30;i++){
if(cs[i].coursename=="#") { //判表是否進入未使用部分
cs[i].coursename=coursename; cs[i].grade=grade; b=false;break;}
else
if(cs[i].coursename==coursename){ //是否已有該課程記錄
cs[i].grade=grade;b=true;break;}}
if(i==30)return0;//成績表滿返回0
if(b)return1;//修改成績返回1
elsereturn2;//登記成績返回2}學生類課程設置函數:intStudent::SetCourse(string【例8.1】由在冊人員類公有派生學生類intStudent::GetCourse(stringcoursename){inti;for(i=0;i<30;i++)
if(cs[i].coursename==coursename)
returncs[i].grade;return-1;}//找到返回成績,未找到返回-1查找學生課程成績函數:【例8.1】由在冊人員類公有派生學生類intStudent【例8.1】由在冊人員類公有派生學生類voidStudent::PrintInfo(){ Person::PrintInfo();
inti; cout<<"學號:"<<NoStudent<<'\n';
for(i=0;i<30;i++)//打印各科成績
if(cs[i].coursename!="#") cout<<cs[i].coursename <<'\t'<<cs[i].grade<<'\n';
else
break; cout<<"--------完--------"<<endl;}打印學生情況函數:【例8.1】由在冊人員類公有派生學生類voidStuden例8.1驗證用主函數:intmain(void){
chartemp[30];
inti,k;Personper1("320102820818161","沈俊",man,19820818,"南京四牌樓2號");Personper2;per2.SetName("朱明");per2.SetSex(woman);per2.SetBirth(19780528);per2.SetId("320102780528162");per2.SetHomeAdd("南京市成賢街9號");per1.PrintInfo();per2.PrintInfo();Studentstu1("320102811226161","朱海鵬",man,19811226,"南京市黃浦路1號","06000123");例8.1驗證用主函數:intmain(void)cout<<"請輸入各科成績:"<<'\n';
//完整的程序應輸入學號,查找,再操作
while(1)//輸入各科成績,輸入"end"停止
{cin>>temp;//輸入格式:物理80
if(!strcmp(temp,"end"))break;cin>>k;i=stu1.SetCourse(temp,k);
if(i==0)cout<<"成績列表已滿!"<<'\n';
else
if(i==1)cout<<"修改成績"<<'\n';
elsecout<<"登記成績"<<'\n';}stu1.PrintInfo();while(1){ cout<<"查詢成績"<<'\n'<<"請輸入科目:"<<'\n'; cin>>temp;
if(!strcmp(temp,"end"))break; k=stu1.GetCourse(temp);
if(k==-1)cout<<"未查到"<<'\n';
elsecout<<k<<'\n';
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 施工場地硬化拆除施工方案
- 承德一模數學試卷
- 青島市容缺施工方案
- 湘師大版道德與法治九上3.2.2《我國教育的發(fā)展》聽課評課記錄
- 滬教版數學九年級上冊24.2《比例線段》聽評課記錄
- 【 七年級數學 上冊】1.2.4 第1課時《絕對值》聽評課記錄2
- 粵人版地理八年級上冊《第一節(jié) 地形》聽課評課記錄7
- 2025年度水電工程質量保證合同
- 2025年度綠色能源項目技術咨詢合同 - 副本
- 2025年度新能源項目股權轉讓合同書模板
- 房地產調控政策解讀
- 山東省濟寧市2025屆高三歷史一輪復習高考仿真試卷 含答案
- 五年級數學(小數乘法)計算題專項練習及答案
- 產前診斷室護理工作總結
- 6S管理知識培訓課件
- 2024-2025學年八年級數學人教版上冊寒假作業(yè)(綜合復習能力提升篇)(含答案)
- 醫(yī)院培訓課件:《猴痘流行病學特點及中國大陸首例猴痘病例調查處置》
- 氫氣-安全技術說明書MSDS
- 產科護士臨床思維能力培養(yǎng)
- 《AP內容介紹》課件
- 醫(yī)生定期考核簡易程序述職報告范文(10篇)
評論
0/150
提交評論