0 前言
本篇文章将讨論派生類和虛函數如何與其他語言功能互相作用。例如通路控制、名字查找、指針和類型轉換等等。 所謂類層次結構也就是類的繼承機構,一個有向無環圖。這裡我們規定基類在上層,子類在下層。例如:
本文按基礎部分和延伸部分進行排版。上篇也就是所謂的基礎篇。
1 通路控制與繼承方式
類中的成員可以是private、protected或public修飾。 ----如果一個成員是private修飾,它的名字隻能由其聲明所在類的成員函數與友元使用。 ----如果一個成員是protected修飾,它的名字隻能由其聲明所在類的成員函數和友元,以及由該類派生類的成員函數和友元使用。 ----如果一個成員是public修飾,它的名字可以由任何函數使用。 下面來說說這些訪控制的一貫用法。 class A{ private: //私用成員 protectted: //保護成員 public: //公用成員 }; class中預設為private成員,而struct中預設為public。 一般類中的資料成員或者函數成員,你不希望别人直接通路,就可将其設為私用部分,而在public中留有使用的接口。舉個例子就是你口袋的人民币肯定要設為私有成員,因為你肯定不希望别人能夠直接通路你的口袋有多少錢。别人要想知道這個資訊,肯定隻有通過你,你就留下了public通路的接口。資料隐藏的簡單公用和私用模型能夠很好地滿足具體類型設計的需要。 protected控制可以了解為你兒子是你的繼承者,如果你口袋的錢這個成員屬于protected,那麼你兒子是可以直接通路的,這下就會出現一個問題。在你不經意的情況下,你兒子會對你口袋的錢數進行修改。這也就對資料的破壞敞開的大門。不過幸好,class中private修飾為預設,對于大量資料存在的基類,供派生類使用的情況總存在一些替代方案。 同樣,繼承方式也分為3類:private繼承、protectedt繼承、public繼承。具體操作如下所示: class Derived:繼承方式 Base{//..........}; ----private繼承,那麼Base的public和protected成員隻能由Derived的成員函數和友元通路,隻有Derived的成員和友元能将Derived* 轉換為Base* 。 ----protected繼承,Base的public和protected成員能由Derived的成員函數和友元以及由Derived派生出的類的成員函數和友元通路。隻有Derived的成員和友元及Derived派生出的類的成員和友元将Derived *轉換成Base *。 ----public繼承,Base的public成員能由任何函數通路使用,protected成員由Derived的成員和友元及Derived的派生類的成員函數和友元通路。任何函數都可将Derived *轉換到Base *。 需要強調的是class的繼承方式如果不寫則為private繼承,struct不寫則為public繼承。
3 多重繼承
這是一個C++有别于Java的地方。Java中隻允許單一繼承,但可利用實作多個接口,或者利用組合類來達到多重繼承的效果。 顯然單繼承就是隻有一個直接基類。多重繼承的具體形式: class C:public A,public B{ //...................... };
3.1 歧義性解析
如果兩個基類中都包含相同名字的成員函數,例如: class A{ //.............. virtual Info* get_debug_info(); }; class B{ //.............. virtual Info* get_debug_info(); }; class C:public A,public B{//............}; C繼承自A,B,則在f()中: void f(C *pf){ Info *tmp = pf->get_debug_info(); //出錯,因為歧義 tmp = pf->A::get_debug_info(); //ok tmp = pf->B::get_debug_info(); //ok } 但是,這樣做看起來會很不優美,而且混亂。我們可以用在派生類中定義新函數的方式來消除這類問題。 class C:public A,public B{ //........... Info *get_debug_info(){ Info * tmp1 = A::get_debug_info(); Info *tmp2 = B::get_debug_info(); return tmp1->merge(tmp2); } }; 這下可以消除歧義,并且在使用的時候看起來也幹淨很多。 另外一種情況,若在不同的基類中使用同樣的名字是一種精心策劃的設計決策,或者使用者希望能夠基于實際參數類型做出選擇,那麼我們該怎麼做呢?來看下面的例子: class A{ public: int f(int); char f(char); //......... }; calss B{ public: double f(double); //......... }; class C:public A,public B{ public: using A::f; using B::f; char f(char); //此處将隐藏A::f(char); C char f(C); }; void g(C &tmp){ tmp.f(1); //A::f(int) tmp.f('a'); //C::f(char) tmp.f(2.0); //B::f(double) tmp.f(tmp); //C::f(C) } 使用聲明可以組合起一組來自不同基類的和來自派生類的重載函數。
3.2 重複的基類
為了描述多個基類的能力,有時一個類可能多次作為基類。例如: class Base{ public: int a; //....... }; class A:public Base{ //....... }; class B:public Base{ //....... }; class C:public A,public B{ //....... };
就會出現如下所示的類層次結構:
那麼C中就會有兩個Base類的副本,有出現歧義性的危險: void f(C *p){ p->a = 0; //錯誤 p->Base::a = 0; //錯誤 p->A::a = 0; // ok p->B::a = 0; //ok } 公共基類不應該表示為兩個分離對象,這種情況就該用虛基類來處理。 class A:public virtual Base{ //........ }; class B:public virtual Base{ //........ }; class C:public A,public B{ //........ }; 類層次結構為:
這時候在C中隻有一個Base的副本,減少占用空間的同時還有助于消除歧義。