繼承與多態(tài).ppt_第1頁
繼承與多態(tài).ppt_第2頁
繼承與多態(tài).ppt_第3頁
繼承與多態(tài).ppt_第4頁
繼承與多態(tài).ppt_第5頁
已閱讀5頁,還剩100頁未讀 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

1、繼承(inheritance)機制是面向?qū)ο蟪绦蛟O計使代碼可以復用的最重要的手段,它允許程序員在保持原有類特性的基礎上進行擴展,增加功能。這樣產(chǎn)生新的類,稱派生類。繼承呈現(xiàn)了面向?qū)ο蟪绦蛟O計的層次結構。體現(xiàn)了由簡單到復雜的認識過程。,第八章 繼承與多態(tài),多態(tài)性(polymorphism)多態(tài)性是考慮在不同層次的類中,以及在同一類中,同名的成員函數(shù)之間的關系問題。函數(shù)的重載,運算符的重載,屬于編譯時的多態(tài)性。以虛函數(shù)為基礎的運行時的多態(tài)性是面向?qū)ο蟪绦蛟O計的標志性特征。 體現(xiàn)了類推和比喻的思想方法。,第八章 繼承與多態(tài),8.1 繼承與派生的概念,8.4 虛基類 (選讀),8.3 多重繼承與派生類

2、成員標識,8. 6 多態(tài)性與虛函數(shù),8.5 派生類應用討論,8.2 派生類的構造函數(shù)與析構函數(shù),8.1 繼承與派生的概念,層次概念是計算機的重要概念。通過繼承(inheritance)的機制可對類(class)分層,提供類型/子類型的關系。 C+通過類派生(class derivation)的機制來支持繼承。被繼承的類稱為基類(base class)或超類(superclass),新產(chǎn)生的類為派生類(derived class)或子類(subclass)。 基類和派生類的集合稱作類繼承層次結構(hierarchy)。 如果基類和派生類共享相同的公有接口,則派生類被稱作基類的子類型(subtyp

3、e)。 派生反映了事物之間的聯(lián)系,事物的共性與個性之間的關系。 派生與獨立設計若干相關的類,前者工作量少,重復的部分可以從基類繼承來,不需要單獨編程。,8.1 繼承與派生的概念,8.1.1 類的派生與繼承,8. 1.2 公有派生與私有派生,由基類派生出派生類的定義的一般形式為 class 派生類名:訪問限定符 基類名1,訪問限定符 基類名2,訪問限定符 基類名n private: 成員表1; /派生類增加或替代的私有成員 public: 成員表2; /派生類增加或替代的公有成員 protected: 成員表3; /派生類增加或替代的保護成員 ;/分號不可少 其中基類1,基類2,是已聲明的類。

4、在派生類定義的類體中給出的成員稱為派生類成員,它們是新增加成員,它們給派生類添加了不同于基類的新的屬性和功能。派生類成員也包括取代基類成員的更新成員。,8.1.1 類的派生與繼承,(a)多重繼承,(b)單繼承,圖8.1 多重繼承與單繼承,一個基類可以直接派生出多個派生類,派生類可以由多個基類共同派生出來,稱多重繼承。,8.1.1 類的派生與繼承,如果一個派生類可以同時有多個基類,稱為多重繼承(multiple-inheritance),這時的派生類同時得到了多個已有類的特征。一個派生類只有一個直接基類的情況稱為單一繼承(single-inheritance)。,編制派生類時可分四步,吸收基類的

5、成員,改造基類成員,發(fā)展新成員,重寫構造函數(shù)與析構函數(shù),8.1.1 類的派生與繼承,不論是數(shù)據(jù)成員,還是函數(shù)成員,除構造函數(shù)與析構函數(shù)外全盤接收,聲明一個和某基類成員同名的新成員,派生類中的新成員就屏蔽了基類同名成員稱為同名覆蓋(override),派生類新成員必須與基類成員不同名,它的加入保證派生類在功能上有所發(fā)展。,8.1.1 類的派生與繼承,上面的步驟就是繼承與派生編程的規(guī)范化步驟。 第二步中,新成員如是成員函數(shù),參數(shù)表也必須一樣,否則是重載。 第三步中,獨有的新成員才是繼承與派生的核心特征。 第四步是重寫構造函數(shù)與析構函數(shù),派生類不繼承這兩種函數(shù)。不管原來的函數(shù)是否可用一律重寫可免出錯

6、。,訪問控制,亦稱為繼承方式,是對基類成員進一步的限制。訪問控制也是三種: 公有(public)方式,亦稱公有繼承 保護(protected)方式,亦稱保護繼承 私有(private)方式, 亦稱私有繼承。,8.1.2 公有派生與私有派生,訪問限定符有兩方面含義:派生類成員(新增成員)函數(shù)對基類(繼承來的)成員的訪問(調(diào)用和操作),和從派生類對象之外對派生類對象中的基類成員的訪問。,公有派生是絕對主流。,派生類的構造函數(shù)的定義形式為: 派生類名:派生類名(參數(shù)總表):基類名1(參數(shù)表1),基類名2(參數(shù)表2),基類名n(參數(shù)表n),成員對象名1(成員對象參數(shù)表1),成員對象名m(成員對象參數(shù)表

7、m) /派生類新增成員的初始化; /所列出的成員對象名全部為新增成員對象的名字,在構造函數(shù)的聲明中,冒號及冒號以后部分必須略去。 所謂不能繼承并不是不能利用,而是把基類的構造函數(shù)作為新的構造函數(shù)的一部分,或者講調(diào)用基類的構造函數(shù)。基類名僅指直接基類,寫了底層基類,編譯器認為出錯。 冒號后的基類名,成員對象名的次序可以隨意,這里的次序與調(diào)用次序無關。,8.2 派生類的構造函數(shù)與析構函數(shù),派生類構造函數(shù)各部分的執(zhí)行次序為: 1.調(diào)用基類構造函數(shù),按它們在派生類定義的先后順序,對基類成員進行初始化。 2.調(diào)用成員對象的構造函數(shù),按它們在類定義中聲明的先后順序,順序調(diào)用。 3.執(zhí)行派生類的構造函數(shù)。,

8、*在派生類構造函數(shù)中,只要基類不是使用缺省構造函數(shù)都要顯式給出基類名和參數(shù)表。 如果基類沒有定義構造函數(shù),則派生類也可以不定義,全部采用系統(tǒng)給定的缺省構造函數(shù)。 如果基類定義了帶有形參表的構造函數(shù)時,派生類就應當定義構造函數(shù)。,析構函數(shù)各部分執(zhí)行次序與構造函數(shù)相反,首先對派生類新增一般成員析構,然后對新增對象成員析構,最后對基類成員析構。,【例8.1】由在冊人員類公有派生學生類,【例8.1】由在冊人員類公有派生學生類。我們希望基類和派生類共享相同的公有接口,只能采用公有派生來實現(xiàn)。,首先來看基類:,class Person string IdPerson;/身份證號,18位數(shù)字 string

9、Name;/姓名 Tsex Sex; /性別enum Tsexmid,man,woman; int Birthday; /生日,格式1986年8月18日寫作19860818 string HomeAddress;/家庭地址,public: Person(string, string,Tsex,int, string); /構造函數(shù) Person(); /缺省的構造函數(shù) Person(); /析構函數(shù),【例8.1】由在冊人員類公有派生學生類,void SetName(string); /修改名字 string GetName()return Name; /提取名字 void SetSex(Tse

10、x sex)Sex=sex; /修改性別 Tsex GetSex()return Sex;/提取性別 void SetId(string id)IdPerson=id;/修改身份證號 string GetId()return IdPerson; /提取身份證號 void SetBirth(int birthday)Birthday=birthday; /修改生日 int GetBirth()return Birthday; /提取生日 void SetHomeAdd(string ); /修改住址 string GetHomeAdd()return HomeAddress; /提取住址 voi

11、d PrintPersonInfo(); /輸出個人信息 ;,接口函數(shù):,【例8.1】由在冊人員類公有派生學生類,派生的學生類:,class Student:public Person /定義派生的學生類 string NoStudent; /學號 course cs30; /30門課程與成績 public: Student(string id, string name,Tsex sex,int birthday, string homeadd, string nostud); /注意派生類構造函數(shù)聲明方式 Student(); /缺省派生類構造函數(shù) Student(); /派生類析構函數(shù) S

12、etCourse(string ,int); /課程設置 int GetCourse(string ); /查找成績 void PrintStudentInfo(); /打印學生情況 ;,struct course char* coursename; int grade;,在VC+平臺上運行例8.1,驗證主函數(shù),8.3 多重繼承與派生類成員標識,由多個基類共同派生出新的派生類,這樣的繼承結構被稱為多重繼承或多繼承(multiple-inheritance),圖8.3 大學在冊人員繼承關系,8.3 多重繼承與派生類成員標識,派生出來的新類同樣可以作為基類再繼續(xù)派生出更新的類,依此類推形成一個層次

13、結構。,例:輸出派生類構造函數(shù)與析構函數(shù)的調(diào)用關系,#include class Base1 int x; public: Base1(int a) x=a; cout調(diào)用基類1的構造函數(shù)!n; Base1( )cout調(diào)用基類1的析構函數(shù)!n; ; class Base2 int y; public: Base2(int a)y=a; cout調(diào)用基類2的構造函數(shù)!n; Base2()cout調(diào)用基類2的析構函數(shù)!n; ;,class Derived:public Base1, public Base2 int z; public: Derived(int a, int b):Base1(a

14、), Base2(20) z=b; cout調(diào)用派生類的構造函數(shù)!n; Derived() cout調(diào)用派生類的析構函數(shù)!n; ; void main(void) Derived c(100,200); ,例:派生類中包含對象成員,#inlucde “l(fā)iti11-4.h” class Der:public Base1, public Base2 int z; Base1 b1, b2; public: Der(int a, int b):Base1(a), Base2(20), b1(200), b2(a+b) z=b; cout調(diào)用派生類的構造函數(shù)!n; Der() cout調(diào)用派生類的析

15、構函數(shù)!n; ; void main(void) Der d(100,200); ,本例中類Circle為圓;類Line為高;類Cone為圓錐,由Circle和Line公有派生而來。在Cone類中,Circle和Line類的接口完全不變,可以直接調(diào)用,這就是公有派生的優(yōu)點。在Cone的成員函數(shù)中可直接訪問Circle和Line中的公有成員,但不能直接訪問私有成員。,【例8.2】由圓和高多重繼承派生出圓錐,檢證主程序:,圓類Circle定義,高類Line定義,圓錐類Cone定義,8.3.1 沖突 在多重派生時,若一個公有的派生類是由兩個或多個基類派生,當基類中成員的訪問權限為public,且不同

16、基類中的成員具有相同的名字時,出現(xiàn)了重名的情況。這時在派生類使用到基類中的同名成員時,出現(xiàn)了不唯一性,這種情況稱為沖突。,class A public: int x; void Show()coutx=xn; A(int a)x=a; A() ; class B public: int x; void Show()coutx=xn; B(int a)x=a; B() ;,class C:public A, public B int y; public: void Setx(int a)x=a; void Sety(int b)y=b; int Gety()return y; ; void ma

17、in(void) C c1; c1.Show(); ,8.3 多重繼承與派生類成員標識(沖突、支配規(guī)則、賦值兼容規(guī)則),下面是編譯時的出錯提示: D:VCsourceL11_7.cpp(19) : error C2385: C:x is ambiguous(不明確的) D:VCsourceL11_7.cpp(19) : warning C4385: could be the x in base A of class C D:VCsourceL11_7.cpp(19) : warning C4385: or the x in base B of class C D:VCsourceL11_7.c

18、pp(26) : error C2385: C:Show is ambiguous D:VCsourceL11_7.cpp(26) : warning C4385: could be the Show in base A of class C D:VCsourceL11_7.cpp(26) : warning C4385: or the Show in base B of class C 說明: 在派生類C中訪問由基類繼承來的變量x時,編譯系統(tǒng)無法確定是要訪問屬于基類A中的x還是屬于基類B中的x,因此編譯出錯。同樣,在基類C的對象c1中調(diào)用函數(shù)Show()時,也是無法確定是要調(diào)用類A中繼承來的

19、公有成員函數(shù)Show(),還是調(diào)用類B中繼承來的公有成員函數(shù)Show()。,解決沖突問題的方法: 各基類中定義的成員函數(shù)名不同; 對于成員數(shù)據(jù),在基類中說明其訪問權限為private,并在相應的基類中提供成員函數(shù)對這些成員數(shù)據(jù)進行操作。 使用作用域分辨符來限定所訪問成員的屬性,其格式為: 類名:成員名;,class C : public A, public B int y; public: void SetAx(int a)A:x=a; /對類A中的x設置 void SetBx(int a)B:x=a;/對類B中的x設置 void Sety(int b)y=b; int Gety()retur

20、n y; ; void main(void) C c1; c1.SetAx(35); c1.SetBx(100); c1.Sety(300); c1.A:Show();/調(diào)用類A中的成員函數(shù) c1.B:Show();/調(diào)用類B中的成員函數(shù) couty=c1.Gety()n; ,注意:當把派生類作為基類,又派生出新的派生類時,這種限定作用域的運算符不能嵌套使用,即:類名1:類名2: . :成員名; 也就是說,限定作用域的運算符只能直接限定其成員。,8.3.2 支配規(guī)則 在C+中,允許派生類中新增加的成員名與其基類的成員名相同,這種相同不產(chǎn)生沖突。當沒有使用作用域運算符時,則派生類中定義的成員名優(yōu)

21、先于基類中的成員名,這種優(yōu)先關系稱為支配規(guī)則。,class A public: int x; void Show()coutx=xn; ; class B public: int y; void Show()couty=yn; ; class C:public A, public B public : int y; ;,void main(void) C c1; c1.x=100; c1.y=200; c1.B:y=300; c1.A:Show(); c1.B:Show(); couty=c1.yn; couty=c1.B:yn; 運行結果為: X=100 Y=300 Y=200 Y=300,

22、8.3.3 繼承和對象成員,C+規(guī)定,任一基類在派生類中只能繼承一次,否則會造成成員名的沖突。 Class Aclass B:public A, public A public: float x;. ; 此時在派生類B中包含了兩個繼承來的成員X,在使用時產(chǎn)生沖突。 解決的方法是,在類B的定義中加入類A的兩個對象作為類B的成員(對象成員)。 Class B A a1, a2; ; 注意:在派生類中,如果權限允許的話可以直接使用基類的成員,但這里使用的是對象成員的成員,必須在對象名后加上成員運算符“.”和成員名。,/例:基類成員與對象成員在使用上的差別 #include class A publi

23、c: int x; A(int a=0)x=a; ; class B public: int y; B(int a=0)y=a; ; class C:public A int z; B b1; public: C(int a, int b, int m):A(a),b1(b) z=m; void show() coutx=xt;/直接使用成員名 couty=b1.yt;/不能直接使用對象的成員名y coutz=zn; ;,void main(void) C c1(100,200,300); c1.show(); ,8.3. 4 賦值兼容規(guī)則 簡單的說就是:對于公有派生類來說,可以將派生類的對象

24、賦給其基類的對象,反之是不允許的。,class A public: int x; . ;,class C: public A, public B public: int y; . ; C c1,c2,c3; A a1, *pa1; B b1, *pb1;,class B public: int y; . ;,不能將基類的對象賦給派生類對象。例如: c2 = a1; c3 = b1; 可以將一個派生類對象的地址賦給基類的指針變量。例如: pa1 = ,說明: 派生類的對象可以賦給基類的對象,系統(tǒng)是將派生類對象中從對應基類中繼承來的成員賦給基類對象。例如: a1 = c1; /將c1中從類A中繼承

25、來的對應成員x分別賦給a1的對應成員。 b1 = c1; /將c1中從類B中繼承來的對應成員y分別賦給b1的對應成員。,8.4 虛基類,圖11-2 派生類中包含同一基類的兩個拷貝,對于某一共公共基類A,設類B由類A公有派生,類C也由類A公有派生,而類D是由類B和類C共同公有派生。這時在類D中將包含類A的兩個拷貝。,基類A,基類B,基類C,類D,public,public,基類A,class A public:int x; A(int a=0)x=a; ; class B:public A/ class B: A(200) x=200 public: int y; B(int a=0, int

26、b=0):A(b)/調(diào)用基類的構造函數(shù) y=a;/y=100, void PB( )coutx=xty=yn; ; class C:public A/ class C: A(400) x=400 public: int z; C(int a=0,int b=0):A(b)/調(diào)用基類的構造函數(shù) z=a;/z=300 void PC( )coutx=xtz=zn; ;,class D: public B, public C public: int m; D(int a, int b, int d, int e, int f):B(a,b),C(d,e) m=f;/ m=500 void Print

27、(void) PB( );/ D PC( );/ E coutm=mn; ; void main(void) D d1(100,200,300,400,500); d1.Print( ); ,注:由于在類D中包含了公共基類A的兩個不同的拷貝,當D、E改為下列情況時: coutx=xty=yn; coutx=xtz=zn;,編譯器認為有錯,因無法確定成員x是從類B中繼承來的還是從類C中繼承來的,從面產(chǎn)生了沖突。為此,必須用作用域運算符來限定成員的屬性:B:x; C:x。 VC+中提供了將基類說明為虛基類的方法,使得在多重派生的過程中公共基類只產(chǎn)生一個拷貝。在派生類的定義中,只要在基類的類名前加上

28、關鍵字virtual,就可以將基類說明為虛基類,格式為: class 類名: vitual 基類名 ;,#include class A public:int x; A(int a=0) x=a; cout虛基類A的構造函數(shù)endl; A()cout虛基類A的析構函數(shù)endl; ;,class B:virtual public A public: int y; B(int a=0, int b=0):A(b) / 不調(diào)用虛基類的構造函數(shù) y=a; cout派生類B的構造函數(shù)endl; B()cout派生類B的析構函數(shù)endl; void PB( )coutx=xty=yn; ; class C

29、:virtual public A public: int z; C(int a=0,int b=0):A(b) / 不調(diào)用虛基類的構造函數(shù) z=a; cout派生類C的構造函數(shù)endl; C( )cout派生類C的析構函數(shù)endl; void PC()coutx=xtz=zn; ;,class D : public B,public C public: int m; D(int a, int b, int d, int e, int f):B(a,b),C(d,e) m=f; cout派生類D的構造函數(shù)endl; D()cout派生類D的析構函數(shù)endl; void Print(void)

30、PB( ); PC( ); coutm=mn; ;,void main(void) D d1(100,200,300,400,500); d1.Print( ); d1.x=400; /對象d1中只有基類A的一個拷貝 d1.Print( ); ,X的初值為0,這是因為調(diào)用虛基類的構造函數(shù)的方法與調(diào)用一般基類的構造函數(shù)的方法不同,編譯器約定,在執(zhí)行類B和類C的構造函數(shù)時都不調(diào)用虛基類A的構造函數(shù),而是在類D的構造函數(shù)中直接調(diào)用虛基類A的缺省的構造函數(shù),使得x的初值為0。因此,虛基類定義中一般要包含有缺省的構造函數(shù)。 2.若虛基類中沒有定義缺省的構造函數(shù),則在派生的每一個派生類的構造函數(shù)的初始化成

31、員例表中都必須有對虛基類構造函數(shù)的調(diào)用。例如將F行改為: A(int a) x=a; 則在類D的構造函數(shù)初始化成員列表中必須增加直接調(diào)用虛基類A的構造函數(shù): D(int a, int b, int d, int e, int f):B(a, b), C(d,e), A(1000) 此時則將類D中的x成員初值置為1000。 3.在派生類D的對象d1中只有基類A的一個拷貝,當改變成員X的值時,由基類B和C中的成員函數(shù)輸出的X的值是相同的;,4. 在派生類對象的創(chuàng)建中,首先是虛基類的構造函數(shù)并按它們聲明的順序構造。第二批是非虛基類的構造函數(shù)按它們聲明的順序調(diào)用。第三批是成員對象的構造函數(shù)。最后是派生

32、類自己的構造函數(shù)被調(diào)用。,書:P207 【例8.3】在采用虛基類的多重繼承中,構造與析構的次序。,class Dclass:public Bclass1,virtual Bclass3,virtual Bclass2 Object object; public: Dclass():object(),Bclass2(),Bclass3(),Bclass1() cout派生類建立!n; Dclass()cout派生類析構!n; void main() Dclass dd; cout主程序運行!n;,運行結果 Constructor Bclass3 /第一個虛擬基類,與派生類析構函數(shù)排列無關 Con

33、structor Bclass2/第二個虛擬基類 Constructor Bclass1/非虛擬基類 Constructor Object/對象成員 派生類建立! 主程序運行! 派生類析構! deconstructor Object/析構次序相反 deconstructor Bclass1 deconstructor Bclass2 deconstructor Bclass3 /析構的次序與構造的次序相反。,8.6 多態(tài)性與虛函數(shù),多態(tài)性是面向?qū)ο蟪绦蛟O計的關鍵技術之一。若程序設計語言不支持多態(tài)性,不能稱為面向?qū)ο蟮恼Z言。利用多態(tài)性技術,可以調(diào)用同一個函數(shù)名的函數(shù),實現(xiàn)完全不同的功能。,在C+

34、中有兩種多態(tài)性,編譯時的多態(tài)性,運行時的多態(tài)性,運行時的多態(tài)性是指在程序執(zhí)行前,無法根據(jù)函數(shù)名和參數(shù)來確定該調(diào)用哪一個函數(shù),必須在程序執(zhí)行過程中,根據(jù)執(zhí)行的具體情況來動態(tài)地確定。它是通過類繼承關系和虛函數(shù)來實現(xiàn)的。目的也是建立一種通用的程序。通用性是程序追求的主要目標之一。,通過函數(shù)的重載和運算符的重載來實現(xiàn)的。,8.6 多態(tài)性與虛函數(shù),8.6.1 虛函數(shù)的定義,8.6.4 動態(tài)聯(lián)編 (選讀),8.6.2 純虛函數(shù),8.6.3 繼承與多態(tài)的應用 單鏈表派生類(選讀),虛函數(shù)既虛擬函數(shù),它必須是成員函數(shù)并且不能是靜態(tài)函數(shù)。虛函數(shù)與成員函數(shù)一樣包含一定的功能,所不同的是虛函數(shù)可以在其派生類的定義中

35、被重新定義。 虛函數(shù)的定義格式如下: Virtual funcname( ); 當在派生類中定義了一個與該虛函數(shù)名同名的成員函數(shù),并且該成員函數(shù)的參數(shù)個數(shù)、參數(shù)的類型以及函數(shù)的返回值類型都與基類中的同名虛函數(shù)一樣,則無論是否使用virtual來修飾該成員函數(shù),它都成為一個虛函數(shù)。,8.6.1 虛函數(shù)的定義,例:使用虛函數(shù) #include const double PI=3.14159; class point double x, y; public: point(float i=0, float j=0) x=i; y=j; virtual double area( )return 0.0;

36、 ; class circle:public point double radius; public: circle(double r=0) radius=r; double area( ) return PI*radius*radius; ;,void main(void) point p; coutarea( )endl; 運行結果: the area of the point p is: 0 the area of the circle c is: 41.8538 now the area is: 41.8538,程序說明: 在基類point中定義了一個虛函數(shù)area( ),其派生類ci

37、rcle中也定義了area( )的成員函數(shù),由于與基類中的虛函數(shù)同名且返回值、形參均相同,因此派生類circle中的area( )也為虛函數(shù)。Main函數(shù)中定義了基類point的對象p,派生類circle的對象c,因此p.area( )以及c.area( )在編譯時就有了確定的含義,既調(diào)用基類point中的成員函數(shù)(虛函數(shù))area( )以及調(diào)用派生類circle的成員函數(shù)(虛函數(shù))area( ),這就是編譯時的多態(tài)性,輸出結果分別為:0、41.8538; C+中,基類指針*pp可以用來指向派生類對象的地址,此時調(diào)用的將是派生類對象的虛函數(shù),既pp-area( )調(diào)用的是派生類circle的成

38、員函數(shù)area( ) 。可見,當基類指針指向不同的派生類對象時,盡管調(diào)用的形式完全相同,但卻是調(diào)用了不同對象中的虛函數(shù),這就是運行時的多態(tài)性。因此,輸出結果仍為:41.8538。,關于虛函數(shù)的幾點說明: 當在基類中把成員函數(shù)定義為虛函數(shù)后,在其派生類中定義的虛函數(shù)必須與基類中的虛函數(shù)同名,且參數(shù)的類型、順序、個數(shù)及函數(shù)的返回值的類型也相同。 要實現(xiàn)動態(tài)(運行時)的多態(tài)性,必須使用基類類型的指針變量,使該指針指向不同派生類的對象,并通過調(diào)用指針所指向的虛函數(shù)才行。 虛函數(shù)必須是類的一個成員函數(shù),它不能是友元函數(shù),或靜態(tài)成員函數(shù)。 在派生類中沒有重新定義虛函數(shù)時,與一般的成員函數(shù)一樣,當調(diào)用這種派

39、生類對象的虛函數(shù)時,則調(diào)用其基類中的虛函數(shù)。 5. 可以把析構函數(shù)定義為虛函數(shù),但是不能將構造函數(shù)定義為虛函數(shù)。 6. 虛函數(shù)與一般的成員函數(shù)相比較,調(diào)用時的執(zhí)行速度稍慢一些,這是因為,為了實現(xiàn)多態(tài)性,在每一個派生類中均要保存相應的虛函數(shù)的入口地址表,函數(shù)的調(diào)用機制也是間接實現(xiàn)的。,思考:在上例中若將基類point中的虛函數(shù)virtual area( )改為一般成員函數(shù),程序運行的結果將會如何? class point double x, y; public: point(float i=0, float j=0) x=i; y=j; double area( )return 0.0; ; 程

40、序運行的結果變?yōu)椋?the area of the point p is: 0 the area of the circle c is: 41.8538 now the area is: 0,結果分析: point *pp; pp=,虛函數(shù)怎樣實現(xiàn)多態(tài)性: 【例8.6】計算學分??捎杀究粕惻缮鲅芯可?,但它們各自的從課程學時數(shù)折算為學分數(shù)的算法是不同的,本科生是16個學時一學分,而研究生是20個學時一學分。,【例8.7】計算學分。派生類定義不再重復。,純虛函數(shù)(pure virtual function)是指被標明為不具體實現(xiàn)的虛擬成員函數(shù)。它用于這樣的情況:定義一個基類時,會遇到無法定義

41、基類中虛函數(shù)的具體實現(xiàn),其實現(xiàn)依賴于不同的派生類。,8.6.2 純虛函數(shù),定義純虛函數(shù)的一般格式為: virtual 返回類型 函數(shù)名(參數(shù)表)=0;,含有純虛函數(shù)的基類是不能用來定義對象的。純虛函數(shù)沒有實現(xiàn)部分,不能產(chǎn)生對象,所以含有純虛函數(shù)的類是抽象類。,1 定義純虛函數(shù)時,不能定義虛函數(shù)的實現(xiàn)部分。即使是函數(shù)體為空也不可以,函數(shù)體為空就可以執(zhí)行,只是什么也不做就返回。而純虛函數(shù)不能調(diào)用。 2 “=0”表明程序員將不定義該函數(shù),函數(shù)聲明是為派生類保留一個位置?!?0”本質(zhì)上是將指向函數(shù)體的指針定為NULL。 3 在派生類中必須有重新定義的純虛函數(shù)的函數(shù)體,這樣的派生類才能用來定義對象。,8

42、.6.2 純虛函數(shù),定義純虛函數(shù)必須注意:,#include class A protected: int x; public: A( )x=1000; virtual void print( ) = 0;/純虛函數(shù) ; class B:public A int y; public: B( )y=2000; void print( )couty=yendl; ; class C:public A int z; public: C( )z=3000; void print( )coutz=zendl; ;,void main(void) A *pa; B b; C c; pa= 也要產(chǎn)生運行錯誤

43、,因為pp的值也是不確定的。,【例8.8】學校對在冊人員進行獎勵,依據(jù)是業(yè)績分,但是業(yè)績分的計算方法只能對具體人員進行,如學生,教師,行政人員,工人,算法都不同,所以可以將在冊人員類作為一個抽象類,業(yè)績計算方法作為一個純虛函數(shù)。 在主函數(shù)中全部用指向基類的指針來調(diào)用,業(yè)績分基類定義,業(yè)績分學生派生類定義,業(yè)績分教師派生類定義,驗證主函數(shù),8.6.3 繼承與多態(tài)的應用單鏈表派生類(選讀),【例8.10】通用單鏈表派生類。首先改造【例7.4】的頭文件,不采用模板類,而采用虛函數(shù)實現(xiàn)多態(tài)性,達到通用的目的。結點類數(shù)據(jù)域被改造為指針,而把數(shù)據(jù)放在一個抽象類中,由指針與之建立聯(lián)系。,圖8.9 結點構造,

44、class Object /數(shù)據(jù)類為抽象類 public: Object() virtual bool operator (Object /析構函數(shù)可為虛函數(shù),構造函數(shù)不行,首先看結點組織,采用結點類加數(shù)據(jù)類,數(shù)據(jù)類定義:,本題兩個要點: 采用虛函數(shù)實現(xiàn)多態(tài)性,達到通用的目的。 堆內(nèi)存的分配與釋放,關鍵不是創(chuàng)建,而是釋放!,數(shù)據(jù)抽象類中含有三個純虛函數(shù):比較函數(shù)和輸出函數(shù)。當抽象類在派生時重新定義純虛函數(shù),可以進行各種類型,包括類和結構對象的比較和輸出。,本例介紹程序總體組成為主,鏈表的操作由學生自己仔細閱讀。,抽象類中的析構函數(shù)也是虛函數(shù),這一點非常重要,當抽象類派生的數(shù)據(jù)類的數(shù)據(jù)部分是動態(tài)

45、產(chǎn)生,而由結點類刪除釋放數(shù)據(jù)類對象時,必須由數(shù)據(jù)類的析構函數(shù)來釋放該類對象數(shù)據(jù)部分占用的動態(tài)分配的內(nèi)存。這時必須重新定義析構函數(shù)。,Class Node Object* info; /數(shù)據(jù)域用指針指向數(shù)據(jù)類對象 Node* link; /指針域 public: Node(); /生成頭結點的構造函數(shù) Node(); /析構函數(shù) void InsertAfter(Node* P); /在當前結點后插入一個結點 Node* RemoveAfter(); /刪除當前結點的后繼結點,返回該結點備用 void Linkinfo(Object* obj); /把數(shù)據(jù)對象連接到結點 friend class

46、 List; /以List為友元類,List可直接訪問Node的私有函數(shù), ;,結點類定義:,class List Node *head,*tail; /鏈表頭指針和尾指針 public: List(); /構造函數(shù),生成頭結點(空鏈表) List(); /析構函數(shù) void MakeEmpty(); /清空鏈表,只余表頭結點 Node* Find(Object /刪除指定結點,定義鏈表類,第二步,由抽象類派生數(shù)據(jù)類(取代模板定義泛型類型為具體類型)步驟是。這里數(shù)據(jù)采用的是字符串,字符串是放在動態(tài)分配的堆內(nèi)存中的,所以析構函數(shù)必須重新定義。為了完成字符串的比較和輸出,重新定義了比較和輸出函數(shù)(

47、虛函數(shù))。,class StringObject:public Object string sptr; public: StringObject() sptr=; StringObject(string s)sptr=s; StringObject(); /析構函數(shù) bool operator(Object ,驗證主函數(shù) 運行結果,在該程序中,特別要仔細揣摩堆內(nèi)存的分配與釋放。刪除一個結點時系統(tǒng)自動調(diào)用結點類析構函數(shù)釋放結點占用的動態(tài)內(nèi)存,而結點類析構函數(shù)自動調(diào)用數(shù)據(jù)域類虛析構函數(shù),數(shù)據(jù)域類析構函數(shù)自動調(diào)用string類的析構函數(shù)釋放所占用的動態(tài)內(nèi)存。一環(huán)套一環(huán),一步都不能錯。這是使用動態(tài)內(nèi)存

48、分配的關鍵。即關鍵不是創(chuàng)建,而是釋放!,在VC+平臺上運行例8.10。,動態(tài)聯(lián)編(dynamic binding)亦稱滯后聯(lián)編(late binding),對應于靜態(tài)聯(lián)編(static binding)。,如果使用對象名和點成員選擇運算符“.”引用特定的一個對象來調(diào)用虛函數(shù),則被調(diào)用的虛函數(shù)是在編譯時確定的(稱為靜態(tài)聯(lián)編),如果使用基類指針或引用指明派生類對象并使用該指針調(diào)用虛函數(shù)(成員選擇符用箭頭號“-”),則程序動態(tài)地(運行時)選擇該派生類的虛函數(shù),稱為動態(tài)聯(lián)編。,8.6.4 動態(tài)聯(lián)編(選讀),聯(lián)編是指計算機程序自身彼此關聯(lián)的過程,是把一個標識符名和一個存儲地址聯(lián)系在一起的過程,也就是把一

49、條消息和一個對象的操作相結合的過程 。,圖8.9 虛函數(shù)調(diào)用的控制流程,8.6.4 動態(tài)聯(lián)編(選讀),C+編譯器編譯含有一個或幾個虛函數(shù)的類及其派生類 時,對該類建立虛函數(shù)表(Virtual function table,vtable)。 虛函數(shù)表使執(zhí)行程序正確選擇每次執(zhí)行時應使用的虛函數(shù)。 多態(tài)是由復雜的數(shù)據(jù)結構實現(xiàn)的,參見圖8.10。圖8.10是以【例8.10】為基礎的,不過增加了一個由抽象類Object派生的復數(shù)數(shù)據(jù)類ComplexObject。圖中列出了基類和各派生類的虛函數(shù)表,這些表是由指向函數(shù)的指針組成的。,8.6.4 動態(tài)聯(lián)編(選讀),還有第二層指針,在實例化帶虛函數(shù)的類(創(chuàng)建對

50、象)時,編譯器在對象前加上一個指向該類的虛函數(shù)表的指針。 第三層指針是鏈表結點類對象中指向抽象基類Object的指針(這也可以是引用,但本例是指針)。 虛函數(shù)的調(diào)用是這樣進行的,考慮虛函數(shù)Compare(),則看含“cat”的結點。由該結點的info指針找到含“cat”的無名對象,再由對象前的指針找到StringObject虛函數(shù)表,移動4個字節(jié)(一個指針占4個字節(jié))找到比較函數(shù)指針,進入串比較函數(shù)。,完,第八章 繼承與派生,謝謝!,Person:Person(string id, string name,Tsex sex,int birthday, string homeadd) IdPer

51、son=id; Name=name; Sex=sex; Birthday=birthday; HomeAddress=homeadd; /作為一個管理程序,這個構造函數(shù)并無必要,因為數(shù)據(jù)總是另外輸入的。僅為說明語法存在。,分析構造函數(shù):,Person:Person() IdPerson=#;Name=#;Sex=mid; Birthday=0;HomeAddress=#; ,分析缺省的構造函數(shù):,分析析構函數(shù):,Person:Person() /string內(nèi)部動態(tài)數(shù)組的釋放,由string自帶的析構函數(shù)完成,void Person:SetName(string name) Name=name

52、; /拷入新姓名 ,修改名字:,void Person:SetHomeAdd(string homeadd) HomeAddress=homeadd; ,修改住址:,void Person:PrintPersonInfo() int i; cout身份證號:IdPersonn姓名: Namen性別:; if(Sex=man)cout男n; else if(Sex=woman)cout女n; else cout n; cout出生年月日:; i=Birthday; couti/10000年; i=i%10000; couti/100月i%100日n 家庭住址:HomeAddressn; ,輸出個

53、人信息:,Student:Student(string id, string name,Tsex sex,int birthday, string homeadd, string nostud) :Person(id,name,sex,birthday,homeadd) /注意Person參數(shù)表不用類型 NoStudent=nostud; for(int i=0;i30;i+) /課程與成績清空 csi.coursename=#; csi.grade=0; ,派生類構造函數(shù):,Student:Student() /基類缺省的無參數(shù)構造函數(shù)不必顯式給出 int i; NoStudent=; fo

54、r(i=0;i30;i+) /課程與成績清零,將來由鍵盤輸入 csi.coursename=; csi.grade=0; ,Student:Student() /基類析構函數(shù)以及成員對象析構函數(shù)自動調(diào)用,缺省派生類構造函數(shù):,派生類析構函數(shù):,int Student:SetCourse(string coursename,int grade) bool b=false; /標識新輸入的課程,還是更新成績 int i; for(i=0;i30;i+) if(csi.coursename=#) /判表是否進入未使用部分(如有對應刪除,應按順序表方式) csi.coursename=coursena

55、me; csi.grade=grade;b=false;break; else if(csi.coursename=coursename) /是否已有該課程記錄 csi.grade=grade;b=true;break; if(i=30) return 0; /成績表滿返回0 if(b) return 1; /修改成績返回1 else return 2; /登記成績返回2 ,學生類課程設置函數(shù):,int Student:GetCourse(string coursename) int i; for(i=0;i30;i+) if(csi.coursename=coursename) return

56、 csi.grade; return -1; /找到返回成績,未找到返回-1,查找學生課程成績函數(shù):,void Student:PrintStudentInfo() int i; cout學號:NoStudentn; PrintPersonInfo(); for(i=0;i30;i+) /打印各科成績 if(csi.coursename!=#) coutcsi.coursename tcsi.graden; else break; cout-完- endl; ,打印學生情況函數(shù):,例8.1驗證用主函數(shù):,int main(void) char temp30; int i,k; Person p

57、er1(320102820818161,沈俊, man,19820818,南京四牌樓2號); Person per2; per2.SetName(朱明); per2.SetSex(woman); per2.SetBirth(19780528); per2.SetId(320102780528162); per2.SetHomeAdd(南京市成賢街9號); per1.PrintPersonInfo(); per2.PrintPersonInfo(); Student stu1(320102811226161,朱海鵬, man,19811226,南京市黃浦路1號,06000123);,couttemp; /輸入格式:物理 80 if(!strcmp(temp,end) break; cink

溫馨提示

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

最新文檔

評論

0/150

提交評論