C程序設(shè)計(自考4737)第6章.ppt_第1頁
C程序設(shè)計(自考4737)第6章.ppt_第2頁
C程序設(shè)計(自考4737)第6章.ppt_第3頁
C程序設(shè)計(自考4737)第6章.ppt_第4頁
C程序設(shè)計(自考4737)第6章.ppt_第5頁
已閱讀5頁,還剩81頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

從已有的對象類型出發(fā)建立一種新的對象類型,使它繼承原對象類型的特點和功能,這種思想是面向?qū)ο笤O(shè)計方法的主要貢獻。 通過對已有類進行特殊化(派生)來建立新的數(shù)據(jù)類型,就使得面向?qū)ο笳Z言具有極大的能力和豐富的表現(xiàn)力。從概念上講,類的派生創(chuàng)建了一種軟件結(jié)構(gòu),它真實地反映了實際問題。從軟件角度來看,類的派生創(chuàng)建了一種類族。派生類的對象也是基類的一種對象,它可以被用在基類對象所使用的任何地方??梢杂枚鄳B(tài)成員函數(shù)仔細(xì)調(diào)整這種關(guān)系,以便使派生類在某些地方與它的基類一致,而在別的地方表現(xiàn)出它自身的行為特征。 本章主要討論C+語言繼承方面的語法特征和一般的使用方法。,第6章 繼承和派生,主要內(nèi)容,6.1 繼承和派生的基本概念 6.2 單一繼承 6.3 多重繼承 6.4 二義性及其支配規(guī)則 6.5 典型問題分析,交通工具分類層次圖的UML表示,交通工具,火車,汽車,飛機,輪船,卡車,旅行車,小汽車,工具車,轎車,面包車,類的派生: 這種通過特殊化已有的類來建立新類的過程,叫做“類的派生”。從類的成員的角度看,派生類自動地將基類的所有成員作為自己的成員,這叫做“繼承”。 基類、派生類: 原有的類叫做“基類”,新建立的類則叫做“派生類”。 基類和派生類又可以分別叫做“父類”和“子類”,有時也稱為“一般類”和“特殊類”。,類的派生和繼承是面向?qū)ο蟪绦蛟O(shè)計方法和C+語言最重要的特征之一。 繼承使得程序員可以在一個較一般的類的基礎(chǔ)上很快地建立一個新類,而不必從零開始設(shè)計每個類。 從一個或多個以前定義的類(基類) 產(chǎn)生新類的過程稱為派生,這個新類稱為派生類。派生的新類同時也可以增加或重新定義數(shù)據(jù)和操作,這就產(chǎn)生了類的層次性。 類的繼承是指新類繼承基類的成員。繼承常用來表示類屬關(guān)系,不能將繼承理解為構(gòu)成關(guān)系。,當(dāng)從現(xiàn)存類中派生出新類時,可以對派生類做如下幾種變化: 可以增加新的數(shù)據(jù)成員; 可以增加新的成員函數(shù); 可以重新定義已有的成員函數(shù); 可以改變現(xiàn)有成員的屬性。,如圖6.1所示,C+中有兩種繼承:單一繼承和多重繼承。對于單一繼承,派生類只能有一個基類;對于多重繼承,派生類可以有多個基類。,圖6.1 類的單一繼承和多重繼承的UML結(jié)構(gòu)圖,C+派生類從父類中繼承性質(zhì)時,可使派生類擴展它們,或者對其做些限制,也可改變或刪除,甚至不作任何修改。所有這些變化可歸結(jié)為兩類基本的面向?qū)ο蠹夹g(shù)。 第一種稱為性質(zhì)約束,即對基類的性質(zhì)加以限制或刪除。第二種稱為性質(zhì)擴展,即增加派生類的性質(zhì)。,派生類的定義格式,單繼承的定義格式如下: Class : ; 多繼承的定義格式如下: Class : , , ; 常使用如下三種關(guān)鍵字給予表示: public 表示公有繼承 private 表示私有繼承 protected 表示保護繼承,派生類的三種繼承方式,公有繼承(public) 基類的公有成員和保護成員作為派生類的成員時,它們都保持原有的狀態(tài),而基類的私有成員仍然是私有的 私有繼承(private) 基類的公有成員和保護成員都作為派生類的私有成員,并且不能被這個派生類的子類所訪問 保護繼承(protected) 基類的所有公有成員和保護成員都作為派生類的保護成員,并且只能被它的派生類成員函數(shù)或友元訪問,基類的私有成員仍然是私有的 系統(tǒng)的默認(rèn)值是私有繼承(private)。,基類和派生類的關(guān)系,任何一個類都可以派生出一個新類,派生類也可以再派生出新類 類A是類C的間接基類, 類B是類A的直接派生類 基類與派生類之間的關(guān)系:可復(fù)用的軟件構(gòu)件 派生類是基類的具體化 派生類是基類定義的延續(xù) 派生類是基類的組合,6.2.1 單一繼承的一般形式 在 C+中,聲明單一繼承的一般形式為: class 派生類名:訪問控制 基類名 private: 成員聲明列表 protected: 成員聲明列表 public: 成員聲明列表 ;,6.2 單一繼承,這里和一般的類的聲明一樣,用關(guān)鍵字class 聲明一個新的類。 冒號后面的部分指示這個新類是哪個基類的派生類。所謂“訪問控制”是指如何控制基類成員在派生類中的訪問屬性,它是3個關(guān)鍵字public, protected和private 中的一個。 一對大括號“ ”中是用來聲明派生類自己的成員的。這和類的聲明一樣,不再贅述。,單繼承,每一個類可以有多個派生類 每一個派生類只能有一個基類從而形成樹形結(jié)構(gòu),成員訪問權(quán)限的控制,公有繼承public 私有繼承private 保護繼承protected,公有繼承(public),公有繼承方式創(chuàng)建的派生類對基類各種成員訪問權(quán)限如下 : 基類公有成員相當(dāng)于派生類的公有成員,即派生類可以象訪問自身公有成員一樣訪問從基類繼承的公有成員。 基類保護成員相當(dāng)于派生類的保護成員,即派生類可以象訪問自身的保護成員一樣,訪問基類的保護成員。 對于基類的私有成員,派生類內(nèi)部成員無法直接訪問。派生類使用者也無法通過派生類對象直接訪問。,例 分析程序中的訪問權(quán)限 #include class A public: void f 1( ); protected: int j1; private: int i1; ;,class B:public A public: void f2( ); protected: int j2; private: int i2; ; class C:public B public: void f3( ); ;,回答下列問題: 派生類B中成員函數(shù)f2( )能否訪問基類A中的成員:f1( ), i1 和 j1呢? 派生類B的對象b1能否訪問基類A中的成員:f1( ), i1 和 j1呢? 派生類C中成員函數(shù)f3( )能否訪問直接基類B中的成員:f2( )和j2呢?能否訪問間接基類A中的成員:f1( ), i1 和 j1 呢? 派生類C的對象c1能否訪問直接基類B中的成員:f2( )和j2呢?能否訪問間接基類A中的成員:f1( ), i1和j1呢? 從對(1)(4)問題的回答可得出什么結(jié)論?,Ans:,可以訪問f1( )和j1,而不可以訪問i1。 可以訪問f1( ),而不可以訪問j1和i1。 可以訪問直接基類中的f2( )和j2以及間接基類中的f1( )和j1,而不可以訪問i2和i1。 可以訪問直接基類中的f2( )和間接基類中的f1( ),其它的都不可以訪問。 在公有繼承時,派生類的成員函數(shù)可訪問基類中的公有成員和保護成員;派生類的對象僅可訪問基類中的公有成員。,私有繼承 (private),派生類對基類各種成員訪問權(quán)限如下 : 基類公有成員和保護成員都相當(dāng)于派生類的私有成員,派生類只能通過自身的函數(shù)成員訪問他們 對于基類的私有成員,無論派生類內(nèi)部成員或派生類使用者都無法直接訪問。,例分析程序,回答問題,#include class A public: void f(int i)cout i endl: void g( ) cout “gn“; ; class B:A public: void h( ) cout “hn“; A:f; ;,void main( ) B d1; d1.f(6); d1.g( ); d1.h( ); ,回答下列問題: 執(zhí)行該程序時,哪個語句會出現(xiàn)編譯錯?為什么? 去掉出錯語句后,執(zhí)行該程序后輸出結(jié)果如何? 程序中派生類B是從基類A繼承來的,這種缺省繼承方式是哪種繼承方式? 派生類B中,A:f的含義是什么? 將派生類B的繼承改為公有繼承方式該程序輸出什么結(jié)果?,Ans:,1 d1.g( );語句出現(xiàn)編譯錯誤,因為B是以私有繼承方式繼承類A的,所以B類的對象不可訪問A類的成員函數(shù)。 2 d1.g( );語句注釋后,執(zhí)行該程序輸出以下結(jié)果: 6 h 3 使用class關(guān)鍵字定義類時,缺省的繼承方式是private。 4 A:f;是將基類中的公有成員說明為派生類的公有成員。 5 將class B:A改為class B:public A以后,輸出如下: 6 g h,保護繼承(public),保護繼承方式創(chuàng)建的派生類對基類各種成員訪問權(quán)限如下 : 基類的公有成員和保護成員都相當(dāng)于派生類的保護成員,派生類可以通過自身的成員函數(shù)或其子類的成員函數(shù)訪問他們 對于基類的私有成員,無論派生類內(nèi)部成員或派生類使用者都無法直接訪問,例 分析程序,回答問題,Include #include class A public: A(const char *nm)strcpy(name, nm); private: char name80; ; class B:public A public: B(const char *nm):A(nm); void PrintName( )const; ;,void B:PrintName( ) const cout “name“ name endl; void main( ) B b1(“wang li“); b1.PrintName( ); ,回答下列問題:,執(zhí)行該程序?qū)霈F(xiàn)什么編譯錯? 對出現(xiàn)的編譯錯如何在訪問權(quán)限上進行修改? 修改后使該程序通過編譯,執(zhí)行執(zhí)行該程序后輸出結(jié)果是什么?,Ans:,1 編譯時出錯行是:cout“name:“nameendl; 錯誤信息提示name是私有成員不能訪問。 2 在類A中,將private改寫為protected。這樣就可以通過編譯。 派生類可訪問基類的保護部分,并把它作為派生類的公有部分;但程序其他部分把name作為私有成員。例如在main中,不能運行 strcpy(s1,) 3 執(zhí)行修改后的該程序輸出如下結(jié)果: wang li,class C:public B public: void f3( ); ;,例 分析程序中的訪問權(quán)限 #include class A public: void f 1( ); protected: int j1; private: int i1; ; class B:public A public: void f2( ); protected: int j2; private: int i2; ;,公有繼承public 私有繼承private 保護繼承rotected,void main( ) B b1 b1.f1( ); b1.f2( ); ,6.2.2 構(gòu)造函數(shù)和析構(gòu)函數(shù),1. 構(gòu)造函數(shù) 派生類對象是由基類中說明的數(shù)據(jù)成員和派生類中說明的數(shù)據(jù)成員共同構(gòu)成 基類中說明的數(shù)據(jù)成員和操作所構(gòu)成的封裝體稱為基類子對象 派生類的構(gòu)造函數(shù)必須通過調(diào)用基類的構(gòu)造函數(shù)類初始化基類子對象 在定義派生類的構(gòu)造函數(shù)時除了對自己的數(shù)據(jù)成員進行初始化外,還必須負(fù)責(zé)調(diào)用基類構(gòu)造函數(shù)使基類的數(shù)據(jù)成員得以初始化。如果派生類中還有子對象時,還應(yīng)包含對子對象初始化的構(gòu)造函數(shù)。,在派生類中繼承的基類成員的初始化,需要由派生類的構(gòu)造函數(shù)調(diào)用基類的構(gòu)造函數(shù)來完成,這和初始化對象成員有類似之處。 定義派生類的構(gòu)造函數(shù)的一般形式為: 派生類名 : 派生類名(參數(shù)表0) : 基類名(參數(shù)表) ./函數(shù)體 ,派生類構(gòu)造函數(shù),派生類構(gòu)造函數(shù)的格式如下: () : (),() ; 派生類構(gòu)造函數(shù)的調(diào)用順序如下: 基類的構(gòu)造函數(shù) 子對象類的構(gòu)造函數(shù)(如果有的話) 派生類構(gòu)造函數(shù),例,#include class A public: A( )a=0; cout“As default constructor called.n“; A(int i)a=i; cout“As constructor called.n“; A( )cout “As destructor called.n“; void Print( ) const cout a “,“; int Geta( )return a; private: int a; ;,class B:public A public: B( )b=0; cout “Bs default constructor called.n“; B(int i, int j, int k); B( )cout “Bs destrutor called.n“; void Print( ); private: int b; A aa; ; B:B(int i, int j, int k) : A(i),aa(j) b = k; cout “Bs constructor called.n“; ,void B:Print( ) A:Print( ); cout b “,“ aa.Geta( ) endl; void main( ) B bb2; bb0 =B(1,2,5); bb1 =B(3,4,7); for(int i=0; i2; i+) bbi.Print( ); ,Ans:,As default constructor called. As default constructor called. 構(gòu)造函數(shù) Bs default constructor called. As default constructor called. As default constructor called. 構(gòu)造函數(shù) Bs default constructor called. As constructor called. As constructor called. Bs constructor called. 賦值 Bs destructor called. As destructor called. As destructor called. As constructor called. As constructor called. Bs constructor called. 賦值 Bs destructor called. As destructor called. As destructor called. 1, 5, 2 3, 7, 4 Bs destructor called. As destructor called. As destructor called. Bs destructor called. As destructor called. As destructor called.,2. 析構(gòu)函數(shù),當(dāng)對象被刪除時,派生類的析構(gòu)函數(shù)被執(zhí)行 由于析構(gòu)函數(shù)不能被繼承,因此在執(zhí)行派生類的析構(gòu)函數(shù)時,基類的析構(gòu)函數(shù)也將被調(diào)用 先執(zhí)行派生類的析構(gòu)函數(shù),再執(zhí)行基類的析構(gòu)函數(shù),例,#include class M public: M( )m1 = m2 = 0; M(int i, int j)m1 = i; m2 = j; void print( )cout m1 “,“ m2 “,“: M( )cout “Ms destructor called.n“; private: int m1, m2; ;,class N:public M public: N( )n = 0; N(int i, int j, int k); void print( )M:print( ); cout n endl; N( )cout “Ns destructor called.n“; private: int n; ; N:N(int i, int j, int k) : M(i,j), n(k) void main( ) N n1(5,6,7), n2(-2,-3,-4); n1.print( ); n2.print( ); ,Ans:,5, 6, 7 -2, -3, -4, Ns destructor called. Ms destructor called. Ns destructor called. Ms destructor called.,如何初始化派生類的對象呢?當(dāng)然也應(yīng)在派生類中聲明一個與派生類同名的函數(shù)。假設(shè)從基類Point派生一個描述矩形的類Rectangle 。類Rectangle繼承Point類的兩個數(shù)據(jù)成員作為矩形的一個頂點,再為Rectangle類增加兩個數(shù)據(jù)成員H 和W,分別描述它的高和寬。為類Point設(shè)計一個構(gòu)造函數(shù)Point(int,int)和顯示數(shù)據(jù)的函數(shù)Showxy。為Rectangle類也設(shè)計構(gòu)造函數(shù)Rectangle(int,int, int, int)和顯示函數(shù)Show。由此可見,派生類增加了兩個新的數(shù)據(jù)成員以及相應(yīng)的成員函數(shù),同時繼承Point的全部成員。 【例6.1】是它們的程序?qū)崿F(xiàn)。 【例6.1】使用默認(rèn)內(nèi)聯(lián)函數(shù)實現(xiàn)單一繼承。 #include using namespace std;,6.2.2 派生類的構(gòu)造函數(shù)和析構(gòu)函數(shù),class Point private: int x,y; public: Point(int a, int b)x=a; y=b; cout“Point.“endl; void Showxy()cout“x=“x“,y=“yendl; Point()cout“Delete Point“endl; ; class Rectangle : public Point private: int H, W;,public: Rectangle(int a, int b, int h, int w):Point(a,b) /構(gòu)造函 /數(shù)初始化列表 H=h; W=w; cout“Rectangle.“endl; void Show()cout“H=“H“,W=“Wendl; Rectangle()cout“Delete Rectangle“endl; ; void main() Rectangle r1(3,4,5,6); r1.Showxy(); /派生類對象調(diào)用基類的成員函數(shù) r1.Show(); /派生類對象調(diào)用派生類的成員函數(shù) ,程序輸出如下: Point. /調(diào)用基類構(gòu)造函數(shù) Rectangle. /調(diào)用派生類構(gòu)造函數(shù) x=3,y=4 /調(diào)用基類成員函數(shù)Showxy() H=5,W=6 /調(diào)用派生類成員函數(shù)Show() Delete Rectangle /調(diào)用派生類析構(gòu)函數(shù) Delete Point /調(diào)用基類析構(gòu)函數(shù) 在派生類中繼承的基類成員的初始化,需要由派生類的構(gòu)造函數(shù)調(diào)用基類的構(gòu)造函數(shù)來完成,這和初始化對象成員有類似之處。 定義派生類的構(gòu)造函數(shù)的一般形式為: 派生類名 : 派生類名(參數(shù)表0) : 基類名(參數(shù)表) ./函數(shù)體 ,冒號后“基類名(參數(shù)表)”稱為成員初始化列表,參數(shù)表給出所調(diào)用的基類構(gòu)造函數(shù)所需要的實參。實參的值可以來自“參數(shù)表0”,或由表達(dá)式給出。可以像Rectangle那樣,在類中直接定義為內(nèi)聯(lián)函數(shù)。下面是在類說明之外定義的示范: Rectangle:Rectangle(int a, int b, int h, int w):Point(a,b) H=h; W=w; cout“Rectangle.“endl; “參數(shù)表0”有4個參數(shù),基類Point的參數(shù)表是自己的2個數(shù)據(jù)成員。 構(gòu)造函數(shù)(包括析構(gòu)函數(shù))是不被繼承的,所以一個派生類只能調(diào)用它的直接基類的構(gòu)造函數(shù)。當(dāng)定義派生類的一個對象時,首先調(diào)用基類的構(gòu)造函數(shù),對基類成員進行初始化,然后執(zhí)行派生類的構(gòu)造函數(shù),如果某個基類仍是一個派生類,則這個過程遞歸進行。當(dāng)該對象消失時,析構(gòu)函數(shù)的執(zhí)行順序和執(zhí)行構(gòu)造函數(shù)時的順序正好相反。輸出結(jié)果也證實了這個結(jié)論。,現(xiàn)在修改Rectangle 的Show函數(shù),使得它可以一次顯示x 、y 、H 和W 。怎樣修改成員函數(shù)Show呢?修改成下面的內(nèi)容能實現(xiàn)這一目的嗎? void Rectangle:show() cout “x=“ x “,y=“y “,H=“ H “,W=“Wendl; 這段簡單程序并不能通過編譯。類Rectangle有4個私有成員x 、y 、H 和W 。這4個私有成員的來源是不一樣的。H 和 W是Rectangle 自己定義的,而x 和y 是從Point那里繼承來的。 換句話說,x和y是類Point的私有成員。類的私有成員是只能被它自己的成員函數(shù)(不討論友元)訪問的,而Show函數(shù)是在類Rectangle 中定義的,它是類Point子類的成員函數(shù),并不是類Point的成員函數(shù),因而不能訪問x 和y 。,6.2.3 類的保護成員,C+語言規(guī)定,公有派生類的成員函數(shù)可直接訪問基類中定義的或基類(從另一個基類)繼承來的公有成員,但不能訪問基類的私有成員。這和私有成員的定義是一致的,符合數(shù)據(jù)封裝思想。但這樣也有問題,就拿上面的程序來說,在類Rectangle 看來,x 、y 、H 和W 的地位是平等的,現(xiàn)在希望對它們“一視同仁”,但C+語言關(guān)于私有成員繼承的規(guī)定卻妨礙這樣做。為解決這一矛盾,C+引入了保護成員的概念。 在類聲明中,關(guān)鍵字protected之后聲明的是類的保護成員。保護成員具有私有成員和公有成員的雙重角色:對派生類的成員函數(shù)而言,它是公有成員,可以被訪問;而對其他函數(shù)而言則仍是私有成員,不能被訪問。因此,要想在類Rectangle 中使用統(tǒng)一的Show函數(shù),只要把x 和y 定義成類Point的保護成員就行了。【例6.2】是修改過的程序。,#include using namespace std; class Point protected: int x,y; public: Point(int a, int b)x=a; y=b; void Show()cout“x=”x“,y=”yendl; /基類的Show()函數(shù) ; class Rectangle : public Point private: int H, W; public: Rectangle(int, int, int, int); /構(gòu)造函數(shù)原型 void Show() cout“x=“x“,y=“y“,H=“H “,W=“Wendl; ;,【例6.2】演示使用protected成員。,/定義構(gòu)造函數(shù) Rectangle:Rectangle(int a, int b, int h, int w):Point(a,b) H=h; W=w; void main() Point a(3,4); Rectangle r1(3,4,5,6); a.Show(); /基類對象調(diào)用基類Show()函數(shù) r1.Show(); /派生類對象調(diào)用派生類Show()函數(shù) 程序還演示了在類體內(nèi)聲明Rectangle構(gòu)造函數(shù)原型,類體外定義它。派生類雖然繼承了基類的成員函數(shù)Show,但它改造了這個函數(shù),使它能顯示所有數(shù)據(jù)。這并不會影響基類函數(shù)原來的功能。程序有意定義了Point的對象a,執(zhí)行a.Show()驗證之。 程序輸出如下: x=3,y=4 x=3,y=4,H=5,W=6 為了將來還可以派生這種新類,建議把H 和W 也定義成保護成員。然后就可以放心大膽地使用統(tǒng)一的Show函數(shù)了。,1. 公有派生和賦值兼容規(guī)則 在前面的例子中,使用了公有派生。在公有派生的情況下,基類成員的訪問權(quán)限在派生類中保持不變。這就意味著在程序中: 基類的公有成員在派生類中仍然是公有的。 基類的保護成員在派生類中仍然是保護的。 基類的不可訪問的和私有的成員在派生類中也仍然是不可訪問的。 所謂“不可訪問”,是說一個成員甚至對于其自身所在類的成員來說也是不可訪問的。這似乎有點兒難以理解,但在類Rectangle 中已經(jīng)遇到過了,x 和y 就是不可訪問的。在根類(不是從別的類派生出來的類)中,沒有成員是不可訪問的。對于根類來說,可能的訪問級別是 private 、public和protected 。但是在派生類中,可以存在第4種訪問級別:不可訪問(inaccessible)。,6.2.4 訪問權(quán)限和賦值兼容規(guī)則,不可訪問成員總是由基類繼承來的,要么是基類的不可訪問成員,要么是基類的私有成員。因此,在公有派生的情況下,可以通過定義派生類自己的成員函數(shù)來訪問派生類對象繼承來的公有和保護成員,但是不能訪問繼承來的私有成員。這一點很重要,當(dāng)希望類的某些成員能夠被子類所訪問,而又不能被其他的外界函數(shù)訪問的時候,就應(yīng)當(dāng)把它們定義為保護的,上一節(jié)就是這樣做的。千萬不能把它們定義成私有的,否則在子類中它們就會是不可訪問的。事實上,當(dāng)這樣做了以后,“每一只黑狗都是狗”,每一個派生類的對象,都是基類的一個對象,于是可以得出賦值兼容規(guī)則:所謂賦值兼容規(guī)則是指在公有派生情況下,一個派生類的對象可以作為基類的對象來使用的情況。 約定類derived 是從類base公有派生而來的,則指如下3種情況:,賦值兼容規(guī)則:所謂賦值兼容規(guī)則是指在公有派生情況下,一個派生類的對象可以作為基類的對象來使用的情況。 約定類derived 是從類base公有派生而來的,則指如下3種情況:, 派生的對象可以賦給基類的對象。例如: derived d; base b; b = d; 派生類的對象可以初始化基類的引用。例如: derived d; base 但要注意,在后兩種情況下,通過pb或br只能訪問對象d 中所繼承的基類成員。下面使用【例6.2】定義的類,編寫一個主程序來演示賦值兼容規(guī)則并說明這一問題。,【例6.3】使用Point和Rectangle類演示賦值兼容規(guī)則的例子。 void main( ) /表演公有繼承的賦值兼容性規(guī)則 Point a(1,2); /基類對象a Rectangle b(3,4,5,6); /派生類對象b a.Show(); b.Show(); Point /派生類對象的地址賦給指向基類的指針,p-Show(); /實際調(diào)用的是基類的Show函數(shù) Rectangle* pb= 程序輸出如下: x=1,y=2 /用a的Show函數(shù)輸出對象a的數(shù)據(jù)成員 x=3,y=4,H=5,W=6 /用b的Show函數(shù)輸出對象b的 /數(shù)據(jù)成員 x=3,y=4 /用b的基類Show函數(shù)輸出其基類數(shù)據(jù)成員,x=3,y=4 /用b的基類Show函數(shù)輸出其基類數(shù)據(jù)成員 x=3,y=4,H=5,W=6 /b的指針調(diào)用Show函數(shù)輸出對 /象b的數(shù)據(jù)成員 x=3,y=4 /用a的Show函數(shù)輸出對象a的數(shù)據(jù)成員 以指針為例,為什么“p-Show();”不是使用b的Show函數(shù)輸出“x=3,y=4,H=5,W=6”?因為“base* pb = ”,從輸出結(jié)果可以看出,它們的含義就是基類用派生類的屬性值代替自己原來的屬性值。這就是公有派生的“isa”原則。,2. “isa”和“ has-a”的區(qū)別 類與類之間的關(guān)系有兩大類。一是繼承和派生問題,二是一個類使用另一個類的問題。后者的簡單用途是把另一個類的對象作為自己的數(shù)據(jù)成員或者成員函數(shù)的參數(shù)。 繼承首先要掌握公有繼承的賦值兼容規(guī)則,理解公有繼承“就是一個(isa)”的含義。如果寫成類B(子類 )公有繼承于類A(父類),在可以使用類A(父類)的對象的任何地方,則類B的對象同樣也能使用,因為每一個類B的對象“就是一個”類A的對象。另一方面,如果需要一個類B的對象,則類A的對象就不行:每個B都是A,但反之則不然。C+強制執(zhí)行公有繼承的這種規(guī)則。例如: class Person .; class Student : Public Person .; 這兩個類所斷言的是:每個學(xué)生都是人,但并不是每個人都是學(xué)生??梢云谕魏我患τ谌藖碚f是真實的事情,對學(xué)生也是真實的,例如他或她都有生日,對于學(xué)生同樣也是真實的。但卻不能期望每一件對于學(xué)生來說是真實的,事情,對所有的人都是真實的。譬如說他或她就讀于一所指定的學(xué)校,這對于一般的人就不能都是真實的?!叭恕钡母拍钜取皩W(xué)生”的概念來得更廣泛些;而“學(xué)生”則是一種特殊類型的“人”。 在C+范圍內(nèi),任何一個要求提供Person類型(或指向Person的指針及引用)參數(shù)的函數(shù),也能夠使用Student對象(或指向Student的指針及引用)作為參數(shù)。例如: void dance(const Person / 對,s是Student,student也是(isa)Person,study(s); / 對 study(p); / 錯! p不都是Student 這只是對公有繼承才是正確的。僅當(dāng)Student類是從Person類中公有地派生出來的時候,C+才具有像上面所描述的那種性質(zhì)。 公共繼承和“isa”的等價性看起來很簡單,但在實際應(yīng)用中并不容易。有時自己的直覺會產(chǎn)生誤導(dǎo)。譬如說,企鵝是鳥是一件事,鳥會飛是另一件事。企鵝是鳥,但不能從鳥會飛誤導(dǎo)出企鵝會飛。 如果已有一個address類,它描述地址這個概念,現(xiàn)在要建立一個worker類,它描述職工這個概念,每個職工都有一個住址,worker類可以有兩種定義形式:使用繼承或?qū)ο蟪蓡T。如果采用繼承,可以按如下形式定義:,class worker:public address / ; 如果在worker類中定義一個address類的類對象成員,可以按如下形式定義: class worker private address workerAddr; / ; 使用繼承的方法是說職工是一個地址,使用對象的方法聲稱職工包含有一個地址屬性。這兩種說法中后者的說法是正確的,即地址只能作為職工的一個屬性,在概念上,它們之間沒有聯(lián)系,所以上例中第2種的描述形式是合理的。這就是分層實現(xiàn)。,由此可見,類用于描述一類對象的共同特性,不同種類的對象之間的聯(lián)系使用繼承來表示,對象所具有的屬性使用類的成員來表示。分層就是一種處理過程,它通過讓分層的類里包含被分層的類的對象作為其數(shù)據(jù)成員,以便把一個類建立在另一些類之上。例如: class String ; / 字符串 class Address ; / 某人居住的地方 class PhoneNumber; class Person private: String name; / 被分層的對象 Address address; / 被分層的對象,PhoneNumber voiceNumber; / 被分層的對象 PhoneNumber faxNumber; / 被分層的對象 public: ; 在這個例子中,Person類要被分層到String、Address和PhoneNumber這幾個類的上面,這是因為它里面含有這些類型的數(shù)據(jù)成員。分層也可以叫做包含、嵌入或者聚合。公有繼承的意思是“isa”。與此相反,分層的意思是指“has-a(有一個)”或者“is-implemented-in-terms-of(是按實現(xiàn)的)”。上面的Person類表示的是一種“has-a”的關(guān)系。一個Person對象有一個名字、一個地址和用于語音及傳真通信的兩個電話號碼。我們不會說一個人就是一個字符串,或者一個人就是一個地址,但會說一個人有一個字符串,并且有一個地址等。,許多人做這種分辨時困難不大,搞不清“isa”和“has-a”之間區(qū)別的人相對來說也很少見。稍微有些傷腦筋的是“isa”和is-implemented-in-terms-of之間的差異,這可通過例子來加深理解。在6.5節(jié)中,將給出分別使用這兩種方法設(shè)計的例子。 3. 公有繼承存取權(quán)限表 派生類一般都使用公有繼承。使用基類的有基類本身、派生類、對象和外部函數(shù),對派生類而言,使用它的有派生類本身、對象和外部函數(shù)。 類中可以使用三種成員,表6.1總結(jié)了它們之間的關(guān)系。,由此可見,保護類型的成員(數(shù)據(jù)成員和成員函數(shù))介于私有和公有之間。對派生類來講,它的作用與public成員一樣。對類的對象、外部函數(shù)以及不屬于本類系之外的類來說,它與private成員一樣,均是不可訪問的,從而保持了類的封裝性。 “訪問控制”決定著基類各成員在派生類中的訪問權(quán)限,上面討論了最常用的公有繼承方式,下面將討論private 和protected繼承方式。,4. 私有派生 通過私有派生,基類的私有和不可訪問成員在派生類中是不可訪問的,而公有和保護成員這時就成了派生類的私有成員,派生類的對象不能訪問繼承的基類成員,必須定義公有的成員函數(shù)作為接口。更重要的是,雖然派生類的成員函數(shù)可通過自定義的函數(shù)訪問基類的成員,但將該派生類作為基類再繼續(xù)派生時,這時即使使用公有派生,原基類公有成員在新的派生類中也將是不可訪問的。下面就來看一個私有派生的例子。 【例6.4】 私有派生的類繼續(xù)派生的例子。,#include using namespace std; class Point /基類定義 private: int x,y; public: Point(int a, int b)x=a; y=b; void Show()cout“x=“x“,y=“yendl; ; class Rectangle : private Point/派生類定義 private: / 新增私有數(shù)據(jù) int H, W; public: /新增外部接口 Rectangle(int a, int b, int h, int w):Point(a,b) H=h; W=w;,void Show()Point:Show(); cout“H=“H“,W=“Wendl; ; class Test: public Rectangle public: Test(int a, int b, int h, int w):Rectangle(a,b,h,w) voidShow()Rectangle:Show(); ;,在這個例子中,基類的公有成員函數(shù)Show通過私有派生成了派生類的私有成員函數(shù),Test雖然是公有派生,但它已經(jīng)無法使用基類的Show函數(shù),即不能通過“Point:Show();”方式使用Point類的Show函數(shù),這就徹底切斷了基類與外界的聯(lián)系。私有派生的這一特點不利于進一步派生,因而實際中私有派生用得并不多。,5. 保護派生 派生也可以使用protected。這種派生使原來的權(quán)限都降一級使用:private變?yōu)椴豢稍L問;protected變?yōu)閜rivate;public變?yōu)閜rotected。因為限制了數(shù)據(jù)成員和成員函數(shù)的訪問權(quán)限,所以用得較少。它與private繼承的區(qū)別主要在下一級的派生中。如果將上例Rectangle改為保護繼承方式,則在Test類中可以使用基類的Show函數(shù),則下面函數(shù)的定義是正確的: class Test: public Rectangle public: Test(int a, int b, int h, int w):Rectangle(a,b,h,w) void Show()Point:Show(); Rectangle:Show(); /可 /以使用基類Point的成員 ;,protected 成員舉例,int main() A a; a.x=5; /錯誤! B b; b.x=5; /錯誤! b. Function(); ,class A protected: int x; class B: public A public: void Function(); ; void B:Function() x=5; ,一個類從多個基類派生的一般形式是: class 類名1:訪問控制 類名2, 訪問控制 類名3 , ., 訪問控制 類名n ./ 定義派生類自己的成員 ; 類名1繼承了類名2到類名n的所有數(shù)據(jù)成員和成員函數(shù),訪問控制用于限制其后的類中的成員在類名1中的訪問權(quán)限,其規(guī)則和單一繼承情況一樣。多重繼承可以視為是單一繼承的擴展。 【例6.5】演示多重繼承的例子。,6.3 多重繼承,#include using namespace std; class A private: int a; public: void setA( int x )a=x; void showA( )cout “a=“ a endl; ; class B private: int b; public: void setB( int x ) b = x; void showB ( ) cou “b=“ b endl; ;,class C : public A, private B private: int c; public: void setC(int x, int y ) c=x; setB(y); void showC( ) showB( ); cout “c=“c endl; ; void main( ) C obj; obj.setA(53); obj.showA( ); /輸出a=53 obj.setC(55,58); obj.showC( ); /輸出b=58 c=55 ,類C從類A公有派生,因此,類A的公有成員(保護成員)在類C中仍是公有的(保護的)。類C從類B私有派生,類B的所有成員在類C中是私有的。這些成員在派生類中的可訪問性和單一繼承中討論的一樣。 類B被私有繼承,因此,類C負(fù)責(zé)維護類B的數(shù)據(jù)成員值和顯示,所以在showC和setC中分別調(diào)用類B的成員函數(shù)showB和setB。使用obj.setB(5)和obj.showB( )都是錯誤的。,6.4.1 二義性和作用域分辨符 對基類成員的訪問必須是無二義性的,如使用一個表達(dá)式的含義能解釋為可以訪問多個基類中的成員,則這種對基類成員的訪問就是不確定的, 稱這種訪問具有二義性。,6.4 二義性及其支配規(guī)則,C類的成員函數(shù)hunc訪問func時,無法確定是訪問基類A還是基類B,出現(xiàn)二義性。使用A : func( )或B : func( )可以解決這種二義性。,【例6.6】 訪問具有二義性的例子。 #include using namespace std; class A public: void func( ) cout“a.func“endl; ;,class B public: void func( ) cout“b.func“endl; Void gunc( ) cout“b.gunc“endl; ; class C : public A, public B public: void gunc( ) cout“c.gunc“endl; void hunc( )func(); / 具有二義性 ;,void main( ) C obj; obj.gunc(); / 不具有二義性,子類覆蓋父類 ,下面是正確的派生類C的實現(xiàn)方法: class C : public A, public B public: void gunc( )cout“c.gunc”endl; void hun1( )A:func(); / 使用基類A的func void hun2( )B:func(); / 使用基類B的func ; 不過,程序仍然含有二義性,如用C類的對象obj訪問函數(shù)func,則具有二義性: obj.func( ); / 不能確定是A的func還是B的func 使用成員名限定可以消除二義性,例如: obj.A : func( ); / A的func obj.B : func( ); / B的func obj.gunc( ); / C的gunc obj. B :gunc( ); / B的gunc,void main( ) C obj obj

溫馨提示

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

最新文檔

評論

0/150

提交評論