C++學習筆記10-面向對象

1.  面向對象編程基於三個基本概念:數據抽象、繼承和動態綁定。

在C++ 中,用類進行數據抽象,用類派生從一個類繼承另一個:派生類繼承基類的成員。動態綁定使編譯器能夠在運行時決定是使用基類中定義的函數還是派生類中定義的函數。在C++ 中,多態性僅用於通過繼承而相關聯的類型的引用或指針。

 

2. 繼承

通過繼承我們能夠定義這樣的類,它們對類型之間的關係建模,共享公共的東西,僅僅特化本質上不同的東西。派生類(derivedclass)能夠繼承基類(baseclass)定義的成員,派生類可以無須改變而使用那些與派生類型具體特性不相關的操作,派生類可以重定義那些與派生類型相關的成員函數,將函數特化,考慮派生類型的特性。最後,除了從基類繼承的成員之外,派生類還可以定義更多的成員。

 

在C++ 中,基類必須指出希望派生類重寫哪些函數,定義爲 virtual 的函數是基類期待派生類重新定義的,基類希望派生類繼承的函數不能定義爲虛函數。


3. 動態綁定

動態綁定我們能夠編寫程序使用繼承層次中任意類型的對象,無須關心對象的具體類型。使用這些類的程序無須區分函數是在基類還是在派生類中定義的。在C++ 中,通過基類的引用(或指針)調用虛函數時,發生動態綁定。引用(或指針)既可以指向基類對象也可以指向派生類對象,這一事實是動態綁定的關鍵。用引用(或指針)調用的虛函數在運行時確定,被調用的函數是引用(或指針)所指對象的實際類型所定義的。

 

4. 基類成員函數

保留字 virtual 的目的是啓用動態綁定。成員默認爲非虛函數,對非虛函數的調用在編譯時確定。爲了指明函數爲虛函數,在其返回類型前面加上保留字virtual。除了構造函數之外,任意非static 成員函數都可以是虛函數。保留字只在類內部的成員函數聲明中出現,不能用在類定義體外部出現的函數定義上。

 

5. 訪問控制和繼承

在基類中,public和private 標號具有普通含義:用戶代碼可以訪問類的public 成員而不能訪問private 成員,private成員只能由基類的成員和友元訪問。派生類對基類的public 和private 成員的訪問權限與程序中任意其他部分一樣:它可以訪問public 成員而不能訪問private 成員。protected 成員可以被派生類對象訪問但不能被該類型的普通用戶訪問。可以認爲protected 訪問標號是private 和public 的混合:

  • 像private 成員一樣,protected成員不能被類的用戶訪問。
  • 像public 成員一樣,protected成員可被該類的派生類訪問。
  • 此外,protected還有另一重要性質: 
  • 派生類只能通過派生類對象訪問其基類的protected 成員,派生類對其基類類型對象的protected 成員沒有特殊訪問權限。

 

6. 派生類

爲了定義派生類,使用類派生列表指定基類。類派生列表指定了一個或多個基類,具有如下形式:

 class classname:access-label base-class

這裏access-label 是public、protected或private,base-class是已定義的類的名字。類派生列表可以指定多個基類。繼承單個基類是爲常見。

 

• 如果是公用繼承,基類成員保持自己的訪問級別:基類的public 成員爲派生類的public 成員,基類的protected 成員爲派生類的protected 成員。

• 如果是受保護繼承,基類的public 和protected 成員在派生類中爲 protected 成員。

• 如果是私有繼承,基類的的所有成員在派生類中爲private 成員。

 

例如,考慮下面的繼承層次:

 class Base {
 public:
 void basemem(); //public member
 protected:
 int i; // protectedmember
 // ...
 };
 struct Public_derived: public Base {
 int use_base() {return i; } // ok: derived classes can access i
 // ...
 };
 structPrivate_derived : private Base {
 int use_base() {return i; } // ok: derived classes can access i
 };

 

無論派生列表中是什麼訪問標號,所有繼承 Base 的類對Base 中的成員具有相同的訪問。派生訪問標號將控制派生類的用戶對從Base 繼承而來的成員的訪問:

 Base b;
 Public_derived d1;
 Private_derived d2;
 b.basemem();         // ok: basemem is public
 d1.basemem();        // ok: basemem is public in the derived class
 d2.basemem();        // error: basemem is private in thederived class
 // 派生訪問標號還控制來自非直接派生類的訪問:
 struct Derived_fromPrivate : public Private_derived {
 // error: Base::i isprivate in Private_derived
 int use_base() {return i; }
 };
 structDerived_from_Public : public Public_derived {
 // ok: Base::iremains protected in Public_derived
 int use_base() {return i; }
 };

 

7. 用作基類的類必須是已定義的

已定義的類纔可以用作基類。如果已經聲明瞭Item_base 類,但沒有定義它,則不能用Item_base 作基類:

 class Item_base; //declared but not defined
 // error: Item_base mustbe defined
 class Bulk_item :public Item_base { ... };

這一限制的原因應該很容易明白:每個派生類包含並且可以訪問其基類的成員,爲了使用這些成員,派生類必須知道它們是什麼。這一規則暗示着不可能從類自身派生出一個類。

 

8. 任何可以在基類對象上執行的操作也可以通過派生類對象使用。

因爲可以使用基類類型的指針或引用來引用派生類型對象,所以,使用基類類型的引用或指針時,不知道指針或引用所綁定的對象的類型:基類類型的引用或指針可以引用基類類型對象,也可以引用派生類型對象。無論實際對象具有哪種類型,編譯器都將它當作基類類型對象。將派生類對象當作基類對象是安全的,因爲每個派生類對象都擁有基類子對象。而且,派生類繼承基類的操作

 

基類類型引用和指針的關鍵點在於靜態類型(在編譯時可知的引用類型或指針類型)和動態類型(指針或引用所綁定的對象的類型這是僅在運行時可知的)可能不同。

 

9. C++ 中的多態性

引用和指針的靜態類型與動態類型可以不同,這是C++ 用以支持多態性的基石。

通過基類引用或指針調用基類中定義的函數時,我們並不知道執行函數的對象的確切類型,執行函數的對象可能是基類類型的,也可能是派生類型的。

 

如果調用非虛函數,則無論實際對象是什麼類型,都執行基類類型所定義的函數。如果調用虛函數,則直到運行時才能確定調用哪個函數,運行的虛函數是引用所綁定的或指針所指向的對象所屬類型定義的版本。 從編寫代碼的角度看我們無需擔心。只要正確地設計和實現了類,不管實際對象是基類類型或派生類型,操作都將完成正確的工作。 另一方面,對象是非多態的——對象類型已知且不變。對象的動態類型總是與靜態類型相同,這一點與引用或指針相反。運行的函數(虛函數或非虛函數)是由對象的類型定義的。

 

只有通過引用或指針調用,虛函數纔在運行時確定。只有在這些情況下,直到運行時才知道對象的動態類型。

 

在編譯時確定非 virtual 調用

 

10. 覆蓋虛函數機制

在某些情況下,希望覆蓋虛函數機制並強制函數調用使用虛函數的特定版本,這裏可以使用作用域操作符:

 Item_base *baseP =&derived;
 // calls version from the base class regardless of the dynamic type of baseP
 double d =baseP->Item_base::net_price(42);

11. 爲什麼會希望覆蓋虛函數機制?

最常見的理由是爲了派生類虛函數調用基類中的版本。在這種情況下,基類版本可以完成繼承層次中所有類型的公共任務,而每個派生類型只添加自己的特殊工作。

派生類虛函數調用基類版本時,必須顯式使用作用域操作符。如果派生類函數忽略了這樣做,則函數調用會在運行時確定並且將是一個自身調用,從而導致無窮遞歸。


12. 繼承與組合

繼承層次的設計本身是個複雜的主題,已超出本書的範圍。但是,有一個重要的設計指南非常基礎,每個程序員都應該熟悉它。定義一個類作爲另一個類的公用派生類時,派生類應反映與基類的“是一種(IsA)”關係。在書店例子中,基類表示按規定價格銷售的書的概念,Bulk_item是一種書,但具有不同的定價策略。

類型之間另一種常見的關係是稱爲“有一個(HasA)”的關係。書店例子中的類具有價格和ISBN。通過“有一個”關係而相關的類型暗含有成員關係,因此,書店例子中的類由表示價格和ISBN 的成員組成。


13. 去除個別成員

如果進行private 或protected 繼承,則基類成員的訪問級別在派生類中比在基類中更受限:

 class Base {
 public:
 std::size_t size()const { return n; }
 protected:
 std::size_t n;
 };
 class Derived :private Base { . . . };  

派生類可以恢復繼承成員的訪問級別,但不能使訪問級別比基類中原來指定的更嚴格或更寬鬆。

 

在這一繼承層次中,size在Base 中爲public,但在Derived 中爲private。爲了使size 在Derived 中成爲public,可以在Derived 的public部分增加一個using 聲明。如下這樣改變Derived 的定義,可以使 size 成員能夠被用戶訪問,並使n 能夠被從Derived 派生的類訪問:

 class Derived :private Base {
 public:
 // maintain access levels for members related to the size of the object
 using Base::size;
 protected:
 using Base::n;
 // ...
 };

14. 默認繼承保護級別

struct 和class 保留字定義的類具有不同的默認訪問級別,同樣,默認繼承訪問級別根據使用哪個保留字定義派生類也不相同。使用class 保留字定義的派生默認具有private 繼承,而用struct 保留字定義的類默認具有public 繼承:

 class Base { /* ...*/ };

 struct D1 : Base { /*... */ };     // public inheritance bydefault

 class D2 : Base { /*... */ };       // private inheritance bydefault


15. 友元關係與繼承

友元關係不能繼承。基類的友元對派生類的成員沒有特殊訪問權限。如果基類被授予友元關係,則只有基類具有特殊訪問權限,該基類的派生類不能訪問授予友元關係的類。

 

每個類控制對自己的成員的友元關係:

 class Base {
 friend class Frnd;
 protected:
 int i;
 };
 // Frnd has no accessto members in D1
 class D1 : publicBase {
 protected:
 int j;
 };
 class Frnd {
 public:
 int mem(Base b) {return b.i; } // ok: Frnd is friend to Base
 int mem(D1 d) {return d.i; }   // error: friendship doesn't inherit
 };
 // D2 has no accessto members in Base
 class D2 : publicFrnd {
 public:
 int mem(Base b) {return b.i; } // error: friendship doesn't inherit
 };

16. 繼承與靜態成員

無論從基類派生出多少個派生類,每個static 成員只有一個實例。static 成員遵循常規訪問控制:如果成員在基類中爲private,則派生類不能訪問它。假定可以訪問成員,則既可以通過基類訪問static 成員,也可以通過派生類訪問static 成員。一般而言,既可以使用作用域操作符也可以使用點或箭頭成員訪問操作符。

 struct Base {
 static voidstatmem();         // public by default
 };
 struct Derived : Base{
 void f(constDerived&);
 };
 void Derived::f(constDerived &derived_obj)
 {
 Base::statmem();                 // ok: Base defines statmem
 Derived::statmem();              // ok: Derived in herits statmem
 // ok: derivedobjects can be used to access static from base
 derived_obj.statmem();           // accessed through Derived object
 statmem();                       // accessed through this class


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章